Skip to content

Commit

Permalink
Merge pull request 99designs#19 from vektah/generate-input-types
Browse files Browse the repository at this point in the history
Generate input models too
  • Loading branch information
vektah authored Feb 22, 2018
2 parents ca50218 + fbd6f2e commit d11d7b8
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 42 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,19 @@ This magic comment tells `go generate` what command to run when we want to regen
go generate ./..
```

*gorunpkg* will build and run the version of gqlgen we just installed into vendor with dep. This makes sure
*gorunpkg* will build and run the version of gqlgen we just installed into vendor with dep. This makes sure
that everyone working on your project generates code the same way regardless which binaries are installed in their gopath.


### Included custom scalar types

Included in gqlgen there are some custom scalar types that will just work out of the box.

- Time: An RFC3339 date as a quoted string
- Map: a json object represented as a map[string]interface{}. Useful change sets.

You are free to redefine these any way you want in types.json, see the [custom scalar example](./example/scalars).

### Prior art

#### neelance/graphql-go
Expand Down
6 changes: 4 additions & 2 deletions codegen/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ func Bind(schema *schema.Schema, userTypes map[string]string, destDir string) (*
bindTypes(imports, namedTypes, prog)

objects := buildObjects(namedTypes, schema, prog, imports)
inputs := buildInputs(namedTypes, schema, prog, imports)
models := append(findMissing(objects), findMissing(inputs)...)

b := &Build{
PackageName: filepath.Base(destDir),
Objects: objects,
Models: findMissingObjects(objects, schema),
Models: models,
Interfaces: buildInterfaces(namedTypes, schema),
Inputs: buildInputs(namedTypes, schema, prog, imports),
Inputs: inputs,
Imports: imports,
}

Expand Down
3 changes: 2 additions & 1 deletion codegen/object_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ func buildObjects(types NamedTypes, s *schema.Schema, prog *loader.Program, impo
return objects
}

func findMissingObjects(objects Objects, s *schema.Schema) Objects {
func findMissing(objects Objects) Objects {
var missingObjects Objects

for _, object := range objects {
if !object.Generated || object.Root {
continue
}
object.GoType = ucFirst(object.GQLType)
object.Marshaler = &Ref{GoType: object.GoType}

for i := range object.Fields {
field := &object.Fields[i]
Expand Down
29 changes: 9 additions & 20 deletions codegen/templates/args.gotpl
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
{{- range $i, $arg := . }}
var arg{{$i}} {{$arg.Signature }}
{{- if eq $arg.GoType "map[string]interface{}" }}
if tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {
{{- if $arg.Type.IsPtr }}
tmp2 := tmp.({{$arg.GoType}})
arg{{$i}} = &tmp2
if tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {
var err error
{{$arg.Unmarshal (print "arg" $i) "tmp" }}
if err != nil {
ec.Error(err)
{{- if $arg.Object.Stream }}
return nil
{{- else }}
arg{{$i}} = tmp.({{$arg.GoType}})
return graphql.Null
{{- end }}
}
{{- else}}
if tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {
var err error
{{$arg.Unmarshal (print "arg" $i) "tmp" }}
if err != nil {
ec.Error(err)
{{- if $arg.Object.Stream }}
return nil
{{- else }}
return graphql.Null
{{- end }}
}
}
{{- end}}
}
{{- end -}}
2 changes: 1 addition & 1 deletion codegen/templates/data.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 44 additions & 6 deletions example/todo/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema {
}

type Resolvers interface {
MyMutation_createTodo(ctx context.Context, text string) (Todo, error)
MyMutation_createTodo(ctx context.Context, todo TodoInput) (Todo, error)
MyMutation_updateTodo(ctx context.Context, id int, changes map[string]interface{}) (*Todo, error)
MyQuery_todo(ctx context.Context, id int) (*Todo, error)
MyQuery_lastTodo(ctx context.Context) (*Todo, error)
Expand All @@ -32,6 +32,11 @@ type Todo struct {
Done bool
}

type TodoInput struct {
Text string
Done *bool
}

type executableSchema struct {
resolvers Resolvers
}
Expand Down Expand Up @@ -93,11 +98,11 @@ func (ec *executionContext) _myMutation(sel []query.Selection) graphql.Marshaler
case "__typename":
out.Values[i] = graphql.MarshalString("MyMutation")
case "createTodo":
var arg0 string
if tmp, ok := field.Args["text"]; ok {
var arg0 TodoInput
if tmp, ok := field.Args["todo"]; ok {
var err error

arg0, err = graphql.UnmarshalString(tmp)
arg0, err = UnmarshalTodoInput(tmp)
if err != nil {
ec.Error(err)
return graphql.Null
Expand All @@ -123,7 +128,13 @@ func (ec *executionContext) _myMutation(sel []query.Selection) graphql.Marshaler
}
var arg1 map[string]interface{}
if tmp, ok := field.Args["changes"]; ok {
arg1 = tmp.(map[string]interface{})
var err error

arg1, err = graphql.UnmarshalMap(tmp)
if err != nil {
ec.Error(err)
return graphql.Null
}
}
res, err := ec.resolvers.MyMutation_updateTodo(ec.ctx, arg0, arg1)
if err != nil {
Expand Down Expand Up @@ -723,7 +734,34 @@ func (ec *executionContext) ___Type(sel []query.Selection, it *introspection.Typ
return out
}

var parsedSchema = schema.MustParse("schema {\n\tquery: MyQuery\n\tmutation: MyMutation\n}\n\ntype MyQuery {\n\ttodo(id: Int!): Todo\n\tlastTodo: Todo\n\ttodos: [Todo!]!\n}\n\ntype MyMutation {\n\tcreateTodo(text: String!): Todo!\n\tupdateTodo(id: Int!, changes: TodoInput!): Todo\n}\n\ntype Todo {\n\tid: Int!\n\ttext: String!\n\tdone: Boolean!\n}\n\ninput TodoInput {\n\ttext: String\n\tdone: Boolean\n}\n")
func UnmarshalTodoInput(v interface{}) (TodoInput, error) {
var it TodoInput

for k, v := range v.(map[string]interface{}) {
switch k {
case "text":
var err error

it.Text, err = graphql.UnmarshalString(v)
if err != nil {
return it, err
}
case "done":
var err error
var ptr1 bool

ptr1, err = graphql.UnmarshalBoolean(v)
it.Done = &ptr1
if err != nil {
return it, err
}
}
}

return it, nil
}

var parsedSchema = schema.MustParse("schema {\n\tquery: MyQuery\n\tmutation: MyMutation\n}\n\ntype MyQuery {\n\ttodo(id: Int!): Todo\n\tlastTodo: Todo\n\ttodos: [Todo!]!\n}\n\ntype MyMutation {\n\tcreateTodo(todo: TodoInput!): Todo!\n\tupdateTodo(id: Int!, changes: Map!): Todo\n}\n\ntype Todo {\n\tid: Int!\n\ttext: String!\n\tdone: Boolean!\n}\n\ninput TodoInput {\n\ttext: String!\n\tdone: Boolean\n}\n")

func (ec *executionContext) introspectSchema() *introspection.Schema {
return introspection.WrapSchema(parsedSchema)
Expand Down
6 changes: 3 additions & 3 deletions example/todo/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ type MyQuery {
}

type MyMutation {
createTodo(text: String!): Todo!
updateTodo(id: Int!, changes: TodoInput!): Todo
createTodo(todo: TodoInput!): Todo!
updateTodo(id: Int!, changes: Map!): Todo
}

type Todo {
Expand All @@ -21,6 +21,6 @@ type Todo {
}

input TodoInput {
text: String
text: String!
done: Boolean
}
11 changes: 7 additions & 4 deletions example/todo/todo.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:generate gorunpkg github.com/vektah/gqlgen -typemap types.json -out generated.go
//go:generate gorunpkg github.com/vektah/gqlgen -out generated.go

package todo

Expand Down Expand Up @@ -47,13 +47,16 @@ func (r *todoResolver) MyQuery_todos(ctx context.Context) ([]Todo, error) {
return r.todos, nil
}

func (r *todoResolver) MyMutation_createTodo(ctx context.Context, text string) (Todo, error) {
func (r *todoResolver) MyMutation_createTodo(ctx context.Context, todo TodoInput) (Todo, error) {
newID := r.id()

newTodo := Todo{
ID: newID,
Text: text,
Done: false,
Text: todo.Text,
}

if todo.Done != nil {
newTodo.Done = *todo.Done
}

r.todos = append(r.todos, newTodo)
Expand Down
2 changes: 1 addition & 1 deletion example/todo/todo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestTodo(t *testing.T) {
var resp struct {
CreateTodo struct{ ID int }
}
c.MustPost(`mutation { createTodo(text:"Fery important") { id } }`, &resp)
c.MustPost(`mutation { createTodo(todo:{text:"Fery important"}) { id } }`, &resp)

require.Equal(t, 4, resp.CreateTodo.ID)
})
Expand Down
3 changes: 0 additions & 3 deletions example/todo/types.json

This file was deleted.

24 changes: 24 additions & 0 deletions graphql/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package graphql

import (
"encoding/json"
"fmt"
"io"
)

func MarshalMap(val map[string]interface{}) Marshaler {
return WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val)
if err != nil {
panic(err)
}
})
}

func UnmarshalMap(v interface{}) (map[string]interface{}, error) {
if m, ok := v.(map[string]interface{}); ok {
return m, nil
}

return nil, fmt.Errorf("%T is not a map", v)
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func loadTypeMap() map[string]string {
"Boolean": "github.com/vektah/gqlgen/graphql.Boolean",
"ID": "github.com/vektah/gqlgen/graphql.ID",
"Time": "github.com/vektah/gqlgen/graphql.Time",
"Map": "github.com/vektah/gqlgen/graphql.Map",
}
if *typemap != "" {
b, err := ioutil.ReadFile(*typemap)
Expand Down
3 changes: 3 additions & 0 deletions neelance/schema/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ var metaSrc = `
# The ` + "`" + `ID` + "`" + ` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as ` + "`" + `"4"` + "`" + `) or integer (such as ` + "`" + `4` + "`" + `) input value will be accepted as an ID.
scalar ID
# The ` + "`" + `Map` + "`" + ` scalar type is a simple json object
scalar Map
# Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.
directive @include(
# Included when true.
Expand Down
3 changes: 3 additions & 0 deletions neelance/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,9 @@ func validateValueType(c *opContext, v common.Literal, t common.Type) (bool, str
if validateBasicLit(lit, t) {
return true, ""
}
} else {
// custom complex scalars will be validated when unmarshaling
return true, ""
}

case *common.List:
Expand Down

0 comments on commit d11d7b8

Please sign in to comment.