diff --git a/codegen/build.go b/codegen/build.go index 15f0282715f..4779ae80582 100644 --- a/codegen/build.go +++ b/codegen/build.go @@ -13,7 +13,6 @@ import ( type Build struct { PackageName string Objects Objects - Models Objects Inputs Objects Interfaces []*Interface Imports Imports @@ -23,12 +22,38 @@ type Build struct { SchemaRaw string } +type ModelBuild struct { + PackageName string + Imports Imports + Models Objects +} + +// Create a list of models that need to be generated +func Models(schema *schema.Schema, userTypes map[string]string, destDir string) *ModelBuild { + namedTypes := buildNamedTypes(schema, userTypes) + + imports := buildImports(namedTypes, destDir) + prog, err := loadProgram(imports, true) + if err != nil { + panic(err) + } + + bindTypes(imports, namedTypes, prog) + + models := buildModels(namedTypes, schema) + return &ModelBuild{ + PackageName: filepath.Base(destDir), + Models: models, + Imports: buildImports(namedTypes, destDir), + } +} + // Bind a schema together with some code to generate a Build func Bind(schema *schema.Schema, userTypes map[string]string, destDir string) (*Build, error) { namedTypes := buildNamedTypes(schema, userTypes) imports := buildImports(namedTypes, destDir) - prog, err := loadProgram(imports) + prog, err := loadProgram(imports, false) if err != nil { return nil, err } @@ -37,12 +62,10 @@ func Bind(schema *schema.Schema, userTypes map[string]string, destDir string) (* 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: models, Interfaces: buildInterfaces(namedTypes, schema), Inputs: inputs, Imports: imports, @@ -83,12 +106,15 @@ func Bind(schema *schema.Schema, userTypes map[string]string, destDir string) (* return b, nil } -func loadProgram(imports Imports) (*loader.Program, error) { - conf := loader.Config{ - AllowErrors: true, - TypeChecker: types.Config{ - Error: func(e error) {}, - }, +func loadProgram(imports Imports, allowErrors bool) (*loader.Program, error) { + conf := loader.Config{} + if allowErrors { + conf = loader.Config{ + AllowErrors: true, + TypeChecker: types.Config{ + Error: func(e error) {}, + }, + } } for _, imp := range imports { if imp.Package != "" { diff --git a/codegen/input_build.go b/codegen/input_build.go index 685c5df8558..508d2853fa6 100644 --- a/codegen/input_build.go +++ b/codegen/input_build.go @@ -40,7 +40,7 @@ func buildInputs(namedTypes NamedTypes, s *schema.Schema, prog *loader.Program, } func buildInput(types NamedTypes, typ *schema.InputObject) *Object { - obj := &Object{NamedType: types[typ.TypeName()]} + obj := &Object{NamedType: types[typ.TypeName()], Input: true} for _, field := range typ.Values { obj.Fields = append(obj.Fields, Field{ diff --git a/codegen/models_build.go b/codegen/models_build.go new file mode 100644 index 00000000000..a3ebb12a634 --- /dev/null +++ b/codegen/models_build.go @@ -0,0 +1,59 @@ +package codegen + +import ( + "sort" + "strings" + + "github.com/vektah/gqlgen/neelance/schema" +) + +func buildModels(types NamedTypes, s *schema.Schema) Objects { + var models Objects + + for _, typ := range s.Types { + var model *Object + switch typ := typ.(type) { + case *schema.Object: + model = buildObject(types, typ, s) + + case *schema.InputObject: + model = buildInput(types, typ) + } + + if model == nil || model.Root || model.GoType != "" { + continue + } + + bindGenerated(types, model) + + models = append(models, model) + } + + sort.Slice(models, func(i, j int) bool { + return strings.Compare(models[i].GQLType, models[j].GQLType) == -1 + }) + + return models +} + +func bindGenerated(types NamedTypes, object *Object) { + object.GoType = ucFirst(object.GQLType) + object.Marshaler = &Ref{GoType: object.GoType} + + for i := range object.Fields { + field := &object.Fields[i] + + if field.IsScalar { + field.GoVarName = ucFirst(field.GQLName) + if field.GoVarName == "Id" { + field.GoVarName = "ID" + } + } else if object.Input { + field.GoFKName = ucFirst(field.GQLName) + field.GoFKType = types[field.GQLType].GoType + } else { + field.GoFKName = ucFirst(field.GQLName) + "ID" + field.GoFKType = "int" // todo: use schema to determine type of id? + } + } +} diff --git a/codegen/object.go b/codegen/object.go index 3c5c14f4d92..e64bb3b59d0 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -17,6 +17,7 @@ type Object struct { Root bool DisableConcurrency bool Stream bool + Input bool } type Field struct { diff --git a/codegen/object_build.go b/codegen/object_build.go index 08c82ea4a0e..d4fbd5a96b0 100644 --- a/codegen/object_build.go +++ b/codegen/object_build.go @@ -16,7 +16,7 @@ func buildObjects(types NamedTypes, s *schema.Schema, prog *loader.Program, impo for _, typ := range s.Types { switch typ := typ.(type) { case *schema.Object: - obj := buildObject(types, typ) + obj := buildObject(types, typ, s) def, err := findGoType(prog, obj.Package, obj.GoType) if err != nil { @@ -30,17 +30,6 @@ func buildObjects(types NamedTypes, s *schema.Schema, prog *loader.Program, impo } } - for name, typ := range s.EntryPoints { - obj := typ.(*schema.Object) - objects.ByName(obj.Name).Root = true - if name == "mutation" { - objects.ByName(obj.Name).DisableConcurrency = true - } - if name == "subscription" { - objects.ByName(obj.Name).Stream = true - } - } - sort.Slice(objects, func(i, j int) bool { return strings.Compare(objects[i].GQLType, objects[j].GQLType) == -1 }) @@ -48,47 +37,7 @@ func buildObjects(types NamedTypes, s *schema.Schema, prog *loader.Program, impo return 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] - - if field.IsScalar { - field.GoVarName = ucFirst(field.GQLName) - if field.GoVarName == "Id" { - field.GoVarName = "ID" - } - } else { - field.GoFKName = ucFirst(field.GQLName) + "ID" - field.GoFKType = "int" - - for _, f := range objects.ByName(field.Type.GQLType).Fields { - if strings.EqualFold(f.GQLName, "id") { - field.GoFKType = f.GoType - } - } - } - } - - missingObjects = append(missingObjects, object) - } - - sort.Slice(missingObjects, func(i, j int) bool { - return strings.Compare(missingObjects[i].GQLType, missingObjects[j].GQLType) == -1 - }) - - return missingObjects -} - -func buildObject(types NamedTypes, typ *schema.Object) *Object { +func buildObject(types NamedTypes, typ *schema.Object, s *schema.Schema) *Object { obj := &Object{NamedType: types[typ.TypeName()]} for _, i := range typ.Interfaces { @@ -118,5 +67,20 @@ func buildObject(types NamedTypes, typ *schema.Object) *Object { Object: obj, }) } + + for name, typ := range s.EntryPoints { + schemaObj := typ.(*schema.Object) + if schemaObj.TypeName() != obj.GQLType { + continue + } + + obj.Root = true + if name == "mutation" { + obj.DisableConcurrency = true + } + if name == "subscription" { + obj.Stream = true + } + } return obj } diff --git a/codegen/templates/data.go b/codegen/templates/data.go index e6f8b10776d..81b5b8aeeac 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -3,9 +3,9 @@ package templates var data = map[string]string{ "args.gotpl": "\t{{- range $i, $arg := . }}\n\t\tvar arg{{$i}} {{$arg.Signature }}\n\t\tif tmp, ok := field.Args[{{$arg.GQLName|quote}}]; ok {\n\t\t\tvar err error\n\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\tif err != nil {\n\t\t\t\tec.Error(err)\n\t\t\t\t{{- if $arg.Object.Stream }}\n\t\t\t\t\treturn nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t{{- end }}\n\t\t\t}\n\t\t} {{ if $arg.Default }} else {\n\t\t\ttmp := {{ $arg.Default | dump }}\n\t\t\tvar err error\n\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\tif err != nil {\n\t\t\t\tec.Error(err)\n\t\t\t\t{{- if $arg.Object.Stream }}\n\t\t\t\t\treturn nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\t\t{{end }}\n\t{{- end -}}\n", "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(field graphql.CollectedField) func() graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\t\tresults, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\tif err != nil {\n\t\t\tec.Error(err)\n\t\t\treturn nil\n\t\t}\n\t\treturn func() graphql.Marshaler {\n\t\t\tres, ok := <-results\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvar out graphql.OrderedMap\n\t\t\tout.Add(field.Alias, func() graphql.Marshaler { {{ $field.WriteJson }} }())\n\t\t\treturn &out\n\t\t}\n\t}\n{{ else }}\n\tfunc (ec *executionContext) _{{$object.GQLType}}_{{$field.GQLName}}(field graphql.CollectedField, {{if not $object.Root}}obj *{{$object.FullName}}{{end}}) graphql.Marshaler {\n\t\t{{- template \"args.gotpl\" $field.Args }}\n\n\t\t{{- if $field.IsConcurrent }}\n\t\t\treturn graphql.Defer(func() graphql.Marshaler {\n\t\t{{- end }}\n\n\t\t\t{{- if $field.GoVarName }}\n\t\t\t\tres := obj.{{$field.GoVarName}}\n\t\t\t{{- else if $field.GoMethodName }}\n\t\t\t\t{{- if $field.NoErr }}\n\t\t\t\t\tres := {{$field.GoMethodName}}({{ $field.CallArgs }})\n\t\t\t\t{{- else }}\n\t\t\t\t\tres, err := {{$field.GoMethodName}}({{ $field.CallArgs }})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tec.Error(err)\n\t\t\t\t\t\treturn graphql.Null\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t{{- else }}\n\t\t\t\tres, err := ec.resolvers.{{ $object.GQLType }}_{{ $field.GQLName }}({{ $field.CallArgs }})\n\t\t\t\tif err != nil {\n\t\t\t\t\tec.Error(err)\n\t\t\t\t\treturn graphql.Null\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t{{ $field.WriteJson }}\n\t\t{{- if $field.IsConcurrent }}\n\t\t\t})\n\t\t{{- end }}\n\t}\n{{ end }}\n", - "file.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\nfunc MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema {\n\treturn &executableSchema{resolvers}\n}\n\ntype Resolvers interface {\n{{- range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{ $field.ResolverDeclaration }}\n\t{{ end }}\n{{- end }}\n}\n\n{{ range $model := .Models }}\n\t{{ template \"model.gotpl\" $model }}\n{{- end}}\n\ntype executableSchema struct {\n\tresolvers Resolvers\n}\n\nfunc (e *executableSchema) Schema() *schema.Schema {\n\treturn parsedSchema\n}\n\nfunc (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tdata := ec._{{.QueryRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"queries are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tdata := ec._{{.MutationRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"mutations are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(op.Selections)\n\t\tif ec.Errors != nil {\n\t\t\treturn graphql.OneShot(&graphql.Response{Data: []byte(\"null\"), Errors: ec.Errors})\n\t\t}\n\n\t\tvar buf bytes.Buffer\n\t\treturn func() *graphql.Response {\n\t\t\tbuf.Reset()\n\t\t\tdata := next()\n\t\t\tif data == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdata.MarshalGQL(&buf)\n\n\t\t\terrs := ec.Errors\n\t\t\tec.Errors = nil\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf.Bytes(),\n\t\t\t\tErrors: errs,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{ {Message: \"subscriptions are not supported\"} }})\n\t{{- end }}\n}\n\ntype executionContext struct {\n\terrors.Builder\n\tresolvers Resolvers\n\tvariables map[string]interface{}\n\tdoc *query.Document\n\tctx context.Context\n}\n\n{{- range $object := .Objects }}\n\t{{ template \"object.gotpl\" $object }}\n\n\t{{- range $field := $object.Fields }}\n\t\t{{ template \"field.gotpl\" $field }}\n\t{{ end }}\n{{- end}}\n\n{{- range $interface := .Interfaces }}\n\t{{ template \"interface.gotpl\" $interface }}\n{{- end }}\n\n{{- range $input := .Inputs }}\n\t{{ template \"input.gotpl\" $input }}\n{{- end }}\n\nvar parsedSchema = schema.MustParse({{.SchemaRaw|quote}})\n\nfunc (ec *executionContext) introspectSchema() *introspection.Schema {\n\treturn introspection.WrapSchema(parsedSchema)\n}\n\nfunc (ec *executionContext) introspectType(name string) *introspection.Type {\n\tt := parsedSchema.Resolve(name)\n\tif t == nil {\n\t\treturn nil\n\t}\n\treturn introspection.WrapType(t)\n}\n", + "generated.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\nfunc MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema {\n\treturn &executableSchema{resolvers}\n}\n\ntype Resolvers interface {\n{{- range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{ $field.ResolverDeclaration }}\n\t{{ end }}\n{{- end }}\n}\n\ntype executableSchema struct {\n\tresolvers Resolvers\n}\n\nfunc (e *executableSchema) Schema() *schema.Schema {\n\treturn parsedSchema\n}\n\nfunc (e *executableSchema) Query(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tdata := ec._{{.QueryRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"queries are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tdata := ec._{{.MutationRoot.GQLType}}(op.Selections)\n\t\tvar buf bytes.Buffer\n\t\tdata.MarshalGQL(&buf)\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf.Bytes(),\n\t\t\tErrors: ec.Errors,\n\t\t}\n\t{{- else }}\n\t\treturn &graphql.Response{Errors: []*errors.QueryError{ {Message: \"mutations are not supported\"} }}\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, doc *query.Document, variables map[string]interface{}, op *query.Operation) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{resolvers: e.resolvers, variables: variables, doc: doc, ctx: ctx}\n\n\t\tnext := ec._{{.SubscriptionRoot.GQLType}}(op.Selections)\n\t\tif ec.Errors != nil {\n\t\t\treturn graphql.OneShot(&graphql.Response{Data: []byte(\"null\"), Errors: ec.Errors})\n\t\t}\n\n\t\tvar buf bytes.Buffer\n\t\treturn func() *graphql.Response {\n\t\t\tbuf.Reset()\n\t\t\tdata := next()\n\t\t\tif data == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdata.MarshalGQL(&buf)\n\n\t\t\terrs := ec.Errors\n\t\t\tec.Errors = nil\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf.Bytes(),\n\t\t\t\tErrors: errs,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(&graphql.Response{Errors: []*errors.QueryError{ {Message: \"subscriptions are not supported\"} }})\n\t{{- end }}\n}\n\ntype executionContext struct {\n\terrors.Builder\n\tresolvers Resolvers\n\tvariables map[string]interface{}\n\tdoc *query.Document\n\tctx context.Context\n}\n\n{{- range $object := .Objects }}\n\t{{ template \"object.gotpl\" $object }}\n\n\t{{- range $field := $object.Fields }}\n\t\t{{ template \"field.gotpl\" $field }}\n\t{{ end }}\n{{- end}}\n\n{{- range $interface := .Interfaces }}\n\t{{ template \"interface.gotpl\" $interface }}\n{{- end }}\n\n{{- range $input := .Inputs }}\n\t{{ template \"input.gotpl\" $input }}\n{{- end }}\n\nvar parsedSchema = schema.MustParse({{.SchemaRaw|quote}})\n\nfunc (ec *executionContext) introspectSchema() *introspection.Schema {\n\treturn introspection.WrapSchema(parsedSchema)\n}\n\nfunc (ec *executionContext) introspectType(name string) *introspection.Type {\n\tt := parsedSchema.Resolve(name)\n\tif t == nil {\n\t\treturn nil\n\t}\n\treturn introspection.WrapType(t)\n}\n", "input.gotpl": "\t{{- if .IsMarshaled }}\n\tfunc Unmarshal{{ .GQLType }}(v interface{}) ({{.FullName}}, error) {\n\t\tvar it {{.FullName}}\n\n\t\tfor k, v := range v.(map[string]interface{}) {\n\t\t\tswitch k {\n\t\t\t{{- range $field := .Fields }}\n\t\t\tcase {{$field.GQLName|quote}}:\n\t\t\t\tvar err error\n\t\t\t\t{{ $field.Unmarshal (print \"it.\" $field.GoVarName) \"v\" }}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn it, err\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t}\n\t\t}\n\n\t\treturn it, nil\n\t}\n\t{{- end }}\n", "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.GQLType}}(sel []query.Selection, obj *{{$interface.FullName}}) graphql.Marshaler {\n\tswitch obj := (*obj).(type) {\n\tcase nil:\n\t\treturn graphql.Null\n\t{{- range $implementor := $interface.Implementors }}\n\tcase {{$implementor.FullName}}:\n\t\treturn ec._{{$implementor.GQLType}}(sel, &obj)\n\n\tcase *{{$implementor.FullName}}:\n\t\treturn ec._{{$implementor.GQLType}}(sel, obj)\n\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", - "model.gotpl": "\ttype {{.GoType}} struct {\n\t\t{{- range $field := .Fields }}\n\t\t\t{{- if $field.GoVarName }}\n\t\t\t\t{{ $field.GoVarName }} {{$field.Signature}}\n\t\t\t{{- else }}\n\t\t\t\t{{ $field.GoFKName }} {{$field.GoFKType}}\n\t\t\t{{- end }}\n\t\t{{- end }}\n\t}\n", + "models.gotpl": "// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT\n\npackage {{ .PackageName }}\n\nimport (\n{{- range $import := .Imports }}\n\t{{- $import.Write }}\n{{ end }}\n)\n\n{{ range $model := .Models }}\n\ttype {{.GoType}} struct {\n\t\t{{- range $field := .Fields }}\n\t\t\t{{- if $field.GoVarName }}\n\t\t\t\t{{ $field.GoVarName }} {{$field.Signature}}\n\t\t\t{{- else }}\n\t\t\t\t{{ $field.GoFKName }} {{$field.GoFKType}}\n\t\t\t{{- end }}\n\t\t{{- end }}\n\t}\n{{- end}}\n", "object.gotpl": "{{ $object := . }}\n\nvar {{ $object.GQLType|lcFirst}}Implementors = {{$object.Implementors}}\n\n// nolint: gocyclo, errcheck, gas, goconst\n{{- if .Stream }}\nfunc (ec *executionContext) _{{$object.GQLType}}(sel []query.Selection) func() graphql.Marshaler {\n\tfields := graphql.CollectFields(ec.doc, sel, {{$object.GQLType|lcFirst}}Implementors, ec.variables)\n\n\tif len(fields) != 1 {\n\t\tec.Errorf(\"must subscribe to exactly one stream\")\n\t\treturn nil\n\t}\n\n\tswitch fields[0].Name {\n\t{{- range $field := $object.Fields }}\n\tcase \"{{$field.GQLName}}\":\n\t\treturn ec._{{$object.GQLType}}_{{$field.GQLName}}(fields[0])\n\t{{- end }}\n\tdefault:\n\t\tpanic(\"unknown field \" + strconv.Quote(fields[0].Name))\n\t}\n}\n{{- else }}\nfunc (ec *executionContext) _{{$object.GQLType}}(sel []query.Selection{{if not $object.Root}}, obj *{{$object.FullName}} {{end}}) graphql.Marshaler {\n\tfields := graphql.CollectFields(ec.doc, sel, {{$object.GQLType|lcFirst}}Implementors, ec.variables)\n\tout := graphql.NewOrderedMap(len(fields))\n\tfor i, field := range fields {\n\t\tout.Keys[i] = field.Alias\n\n\t\tswitch field.Name {\n\t\tcase \"__typename\":\n\t\t\tout.Values[i] = graphql.MarshalString({{$object.GQLType|quote}})\n\t\t{{- range $field := $object.Fields }}\n\t\tcase \"{{$field.GQLName}}\":\n\t\t\tout.Values[i] = ec._{{$object.GQLType}}_{{$field.GQLName}}(field{{if not $object.Root}}, obj{{end}})\n\t\t{{- end }}\n\t\tdefault:\n\t\t\tpanic(\"unknown field \" + strconv.Quote(field.Name))\n\t\t}\n\t}\n\n\treturn out\n}\n{{- end }}\n", } diff --git a/codegen/templates/file.gotpl b/codegen/templates/generated.gotpl similarity index 97% rename from codegen/templates/file.gotpl rename to codegen/templates/generated.gotpl index 61d1f9b0509..fd6d123e115 100644 --- a/codegen/templates/file.gotpl +++ b/codegen/templates/generated.gotpl @@ -20,10 +20,6 @@ type Resolvers interface { {{- end }} } -{{ range $model := .Models }} - {{ template "model.gotpl" $model }} -{{- end}} - type executableSchema struct { resolvers Resolvers } diff --git a/codegen/templates/model.gotpl b/codegen/templates/models.gotpl similarity index 51% rename from codegen/templates/model.gotpl rename to codegen/templates/models.gotpl index 145c41fda43..857a960c19c 100644 --- a/codegen/templates/model.gotpl +++ b/codegen/templates/models.gotpl @@ -1,3 +1,14 @@ +// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT + +package {{ .PackageName }} + +import ( +{{- range $import := .Imports }} + {{- $import.Write }} +{{ end }} +) + +{{ range $model := .Models }} type {{.GoType}} struct { {{- range $field := .Fields }} {{- if $field.GoVarName }} @@ -7,3 +18,4 @@ {{- end }} {{- end }} } +{{- end}} diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 54a9b1b2c8e..ed9c1c7577b 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -9,11 +9,9 @@ import ( "strings" "text/template" "unicode" - - "github.com/vektah/gqlgen/codegen" ) -func Run(e *codegen.Build) (*bytes.Buffer, error) { +func Run(name string, tpldata interface{}) (*bytes.Buffer, error) { t := template.New("").Funcs(template.FuncMap{ "ucFirst": ucFirst, "lcFirst": lcFirst, @@ -29,7 +27,7 @@ func Run(e *codegen.Build) (*bytes.Buffer, error) { } buf := &bytes.Buffer{} - err := t.Lookup("file.gotpl").Execute(buf, e) + err := t.Lookup(name).Execute(buf, tpldata) if err != nil { return nil, err } diff --git a/codegen/type.go b/codegen/type.go index 1e6e76d3370..9df6b2aba64 100644 --- a/codegen/type.go +++ b/codegen/type.go @@ -13,7 +13,6 @@ type NamedType struct { IsInterface bool GQLType string // Name of the graphql type Marshaler *Ref // If this type has an external marshaler this will be set - Generated bool // will it be autogenerated? } type Ref struct { diff --git a/codegen/type_build.go b/codegen/type_build.go index 50fa32dc833..95b66516d6b 100644 --- a/codegen/type_build.go +++ b/codegen/type_build.go @@ -23,8 +23,6 @@ func buildNamedTypes(s *schema.Schema, userTypes map[string]string) NamedTypes { if userType != "" { t.Package, t.GoType = pkgAndType(userType) - } else { - t.Generated = true } types[t.GQLType] = t diff --git a/codegen/util.go b/codegen/util.go index 1603553b2b2..31552cf9815 100644 --- a/codegen/util.go +++ b/codegen/util.go @@ -136,9 +136,7 @@ func bindObject(t types.Type, object *Object, imports Imports) { } default: - if !field.Generated { - fmt.Fprintf(os.Stderr, "type mismatch on %s.%s, expected %s got %s\n", object.GQLType, field.GQLName, field.Type.FullSignature(), structField.Type()) - } + fmt.Fprintf(os.Stderr, "type mismatch on %s.%s, expected %s got %s\n", object.GQLType, field.GQLName, field.Type.FullSignature(), structField.Type()) } continue } diff --git a/example/chat/generated.go b/example/chat/generated.go index 960419eaa72..7467abdb627 100644 --- a/example/chat/generated.go +++ b/example/chat/generated.go @@ -6,7 +6,6 @@ import ( "bytes" context "context" strconv "strconv" - time "time" graphql "github.com/vektah/gqlgen/graphql" errors "github.com/vektah/gqlgen/neelance/errors" @@ -26,13 +25,6 @@ type Resolvers interface { Subscription_messageAdded(ctx context.Context, roomName string) (<-chan Message, error) } -type Message struct { - ID string - Text string - CreatedBy string - CreatedAt time.Time -} - type executableSchema struct { resolvers Resolvers } diff --git a/example/chat/models_gen.go b/example/chat/models_gen.go new file mode 100644 index 00000000000..8734491ccc9 --- /dev/null +++ b/example/chat/models_gen.go @@ -0,0 +1,14 @@ +// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT + +package chat + +import ( + time "time" +) + +type Message struct { + ID string + Text string + CreatedBy string + CreatedAt time.Time +} diff --git a/example/dataloader/generated.go b/example/dataloader/generated.go index 026116ad9bd..188d4ef43e8 100644 --- a/example/dataloader/generated.go +++ b/example/dataloader/generated.go @@ -6,7 +6,6 @@ import ( "bytes" context "context" strconv "strconv" - time "time" graphql "github.com/vektah/gqlgen/graphql" errors "github.com/vektah/gqlgen/neelance/errors" @@ -28,30 +27,6 @@ type Resolvers interface { Query_torture(ctx context.Context, customerIds [][]int) ([][]Customer, error) } -type Address struct { - ID int - Street string - Country string -} - -type Customer struct { - ID int - Name string - AddressID int - OrdersID int -} - -type Item struct { - Name string -} - -type Order struct { - ID int - Date time.Time - Amount float64 - ItemsID int -} - type executableSchema struct { resolvers Resolvers } diff --git a/example/dataloader/models_gen.go b/example/dataloader/models_gen.go new file mode 100644 index 00000000000..2101ea3474a --- /dev/null +++ b/example/dataloader/models_gen.go @@ -0,0 +1,28 @@ +// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT + +package dataloader + +import ( + time "time" +) + +type Address struct { + ID int + Street string + Country string +} +type Customer struct { + ID int + Name string + AddressID int + OrdersID int +} +type Item struct { + Name string +} +type Order struct { + ID int + Date time.Time + Amount float64 + ItemsID int +} diff --git a/example/todo/generated.go b/example/todo/generated.go index 79fc1579814..f37b2764241 100644 --- a/example/todo/generated.go +++ b/example/todo/generated.go @@ -26,17 +26,6 @@ type Resolvers interface { MyQuery_todos(ctx context.Context) ([]Todo, error) } -type Todo struct { - ID int - Text string - Done bool -} - -type TodoInput struct { - Text string - Done *bool -} - type executableSchema struct { resolvers Resolvers } diff --git a/example/todo/models_gen.go b/example/todo/models_gen.go new file mode 100644 index 00000000000..11b41415d49 --- /dev/null +++ b/example/todo/models_gen.go @@ -0,0 +1,13 @@ +// This file was generated by github.com/vektah/gqlgen, DO NOT EDIT + +package todo + +type Todo struct { + ID int + Text string + Done bool +} +type TodoInput struct { + Text string + Done *bool +} diff --git a/main.go b/main.go index 6d51892da0b..dda727ef074 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,12 @@ import ( "encoding/json" "flag" "fmt" + "go/build" "io/ioutil" "os" "path/filepath" + "strings" + "syscall" "github.com/vektah/gqlgen/codegen" "github.com/vektah/gqlgen/codegen/templates" @@ -15,6 +18,7 @@ import ( ) var output = flag.String("out", "-", "the file to write to, - for stdout") +var models = flag.String("models", "models_gen.go", "the file to write the models to") var schemaFilename = flag.String("schema", "schema.graphql", "the graphql schema to generate types from") var typemap = flag.String("typemap", "", "a json map going from graphql to golang types") var packageName = flag.String("package", "", "the package name") @@ -45,7 +49,30 @@ func main() { os.Exit(1) } - build, err := codegen.Bind(schema, loadTypeMap(), dirName()) + if *output != "-" { + _ = syscall.Unlink(*output) + } + _ = syscall.Unlink(*models) + + types := loadTypeMap() + + modelsBuild := codegen.Models(schema, types, dirName()) + if len(modelsBuild.Models) > 0 { + buf, err := templates.Run("models.gotpl", modelsBuild) + if err != nil { + fmt.Fprintf(os.Stderr, "unable to generate code: "+err.Error()) + os.Exit(1) + } + + write(*models, buf.Bytes()) + pkgName := fullPackageName() + + for _, model := range modelsBuild.Models { + types[model.GQLType] = pkgName + "." + model.GoType + } + } + + build, err := codegen.Bind(schema, types, dirName()) if err != nil { fmt.Fprintln(os.Stderr, "failed to generate code: "+err.Error()) os.Exit(1) @@ -56,38 +83,42 @@ func main() { build.PackageName = *packageName } - buf, err := templates.Run(build) + buf, err := templates.Run("generated.gotpl", build) if err != nil { fmt.Fprintf(os.Stderr, "unable to generate code: "+err.Error()) os.Exit(1) } - if *output == "-" { - fmt.Println(string(gofmt(*output, buf.Bytes()))) + write(*output, buf.Bytes()) +} + +func gofmt(filename string, b []byte) []byte { + out, err := imports.Process(filename, b, nil) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to gofmt: "+err.Error()) + return b + } + return out +} + +func write(filename string, b []byte) { + if filename == "-" { + fmt.Println(string(gofmt(filename, b))) } else { - err := os.MkdirAll(filepath.Dir(*output), 0755) + err := os.MkdirAll(filepath.Dir(filename), 0755) if err != nil { fmt.Fprintln(os.Stderr, "failed to create directory: ", err.Error()) os.Exit(1) } - err = ioutil.WriteFile(*output, gofmt(*output, buf.Bytes()), 0644) + err = ioutil.WriteFile(filename, gofmt(filename, b), 0644) if err != nil { - fmt.Fprintln(os.Stderr, "failed to write output: ", err.Error()) + fmt.Fprintf(os.Stderr, "failed to write %s: %s", filename, err.Error()) os.Exit(1) } } } -func gofmt(filename string, b []byte) []byte { - out, err := imports.Process(filename, b, nil) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to gofmt: "+err.Error()) - return b - } - return out -} - func absOutput() string { absPath, err := filepath.Abs(*output) if err != nil { @@ -100,6 +131,25 @@ func dirName() string { return filepath.Dir(absOutput()) } +func fullPackageName() string { + absPath, err := filepath.Abs(*output) + if err != nil { + panic(err) + } + pkgName := filepath.Dir(absPath) + if *packageName != "" { + pkgName = filepath.Join(filepath.Dir(pkgName), *packageName) + } + + for _, gopath := range strings.Split(build.Default.GOPATH, ":") { + gopath += "/src/" + if strings.HasPrefix(pkgName, gopath) { + pkgName = pkgName[len(gopath):] + } + } + return pkgName +} + func loadTypeMap() map[string]string { goTypes := map[string]string{ "__Directive": "github.com/vektah/gqlgen/neelance/introspection.Directive",