Skip to content

Commit

Permalink
Merge pull request #502 from 99designs/model-plugin
Browse files Browse the repository at this point in the history
Model plugin
  • Loading branch information
vektah authored Jan 26, 2019
2 parents f94b4b7 + 0f88449 commit 62175ea
Show file tree
Hide file tree
Showing 33 changed files with 815 additions and 412 deletions.
6 changes: 3 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ func Execute() {
app.Before = func(context *cli.Context) error {
pwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("unable to determine current workding dir: %s\n", err.Error())
return fmt.Errorf("unable to determine current workding dir: %s", err.Error())
}

if !gopath.Contains(pwd) {
return fmt.Errorf("gqlgen must be run from inside your $GOPATH\n")
return fmt.Errorf("gqlgen must be run from inside your $GOPATH")
}
if context.Bool("verbose") {
log.SetFlags(0)
Expand All @@ -47,7 +47,7 @@ func Execute() {
}

if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, err.Error())
fmt.Fprint(os.Stderr, err.Error())
os.Exit(1)
}
}
45 changes: 6 additions & 39 deletions codegen/build.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
package codegen

import (
"fmt"
"go/types"
"sort"

"fmt"

"github.com/99designs/gqlgen/codegen/config"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
"golang.org/x/tools/go/loader"
)

type builder struct {
Config *config.Config
Schema *ast.Schema
SchemaStr map[string]string
Program *loader.Program
Binder *config.Binder
Directives map[string]*Directive
NamedTypes NamedTypes
}
Expand All @@ -37,10 +35,9 @@ func buildSchema(cfg *config.Config) (*Schema, error) {
return nil, err
}

progLoader := b.Config.NewLoaderWithoutErrors()
b.Program, err = progLoader.Load()
b.Binder, err = b.Config.NewBinder()
if err != nil {
return nil, errors.Wrap(err, "loading failed")
return nil, err
}

b.NamedTypes = NamedTypes{}
Expand Down Expand Up @@ -120,7 +117,7 @@ func (b *builder) injectIntrospectionRoots(s *Schema) error {
return fmt.Errorf("root query type must be defined")
}

typeType, err := b.FindGoType("github.com/99designs/gqlgen/graphql/introspection", "Type")
typeType, err := b.Binder.FindObject("github.com/99designs/gqlgen/graphql/introspection", "Type")
if err != nil {
return errors.Wrap(err, "unable to find root Type introspection type")
}
Expand All @@ -145,7 +142,7 @@ func (b *builder) injectIntrospectionRoots(s *Schema) error {
Object: obj,
})

schemaType, err := b.FindGoType("github.com/99designs/gqlgen/graphql/introspection", "Schema")
schemaType, err := b.Binder.FindObject("github.com/99designs/gqlgen/graphql/introspection", "Schema")
if err != nil {
return errors.Wrap(err, "unable to find root Schema introspection type")
}
Expand All @@ -161,33 +158,3 @@ func (b *builder) injectIntrospectionRoots(s *Schema) error {

return nil
}

func (b *builder) FindGoType(pkgName string, typeName string) (types.Object, error) {
if pkgName == "" {
return nil, nil
}
fullName := typeName
if pkgName != "" {
fullName = pkgName + "." + typeName
}

pkgName, err := resolvePkg(pkgName)
if err != nil {
return nil, errors.Errorf("unable to resolve package for %s: %s\n", fullName, err.Error())
}

pkg := b.Program.Imported[pkgName]
if pkg == nil {
return nil, errors.Errorf("required package was not loaded: %s", fullName)
}

for astNode, def := range pkg.Defs {
if astNode.Name != typeName || def.Parent() == nil || def.Parent() != pkg.Pkg.Scope() {
continue
}

return def, nil
}

return nil, errors.Errorf("unable to find type %s\n", fullName)
}
6 changes: 4 additions & 2 deletions codegen/build_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"log"
"strings"

"github.com/99designs/gqlgen/codegen/templates"

"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
)
Expand Down Expand Up @@ -79,7 +81,7 @@ func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, e
TypeReference: b.NamedTypes.getType(field.Type),
Object: obj,
Directives: dirs,
GoFieldName: lintName(ucFirst(field.Name)),
GoFieldName: templates.ToGo(field.Name),
GoFieldType: GoFieldVariable,
GoReceiverName: "obj",
}
Expand All @@ -99,7 +101,7 @@ func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, e
f.IsResolver = true
}
if typeField.FieldName != "" {
f.GoFieldName = lintName(ucFirst(typeField.FieldName))
f.GoFieldName = templates.ToGo(typeField.FieldName)
}
}
}
Expand Down
15 changes: 7 additions & 8 deletions codegen/build_typedef.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ func (b *builder) buildTypeDef(schemaType *ast.Definition) (*TypeDefinition, err
}

// External marshal functions
def, _ := b.FindGoType(pkgName, "Marshal"+typeName)
def, err := b.Binder.FindObject(pkgName, typeName)
if err != nil {
return nil, err
}
if f, isFunc := def.(*types.Func); isFunc {
sig := def.Type().(*types.Signature)
t.GoType = sig.Params().At(0).Type()
t.Marshaler = f

unmarshal, err := b.FindGoType(pkgName, "Unmarshal"+typeName)
unmarshal, err := b.Binder.FindObject(pkgName, "Unmarshal"+typeName)
if err != nil {
return nil, errors.Wrapf(err, "unable to find unmarshal func for %s.%s", pkgName, typeName)
}
Expand All @@ -59,13 +62,9 @@ func (b *builder) buildTypeDef(schemaType *ast.Definition) (*TypeDefinition, err
}

// Normal object binding
obj, err := b.FindGoType(pkgName, typeName)
if err != nil {
return nil, errors.Wrapf(err, "unable to find %s.%s", pkgName, typeName)
}
t.GoType = obj.Type()
t.GoType = def.Type()

namedType := obj.Type().(*types.Named)
namedType := def.Type().(*types.Named)
hasUnmarshal := false
for i := 0; i < namedType.NumMethods(); i++ {
switch namedType.Method(i).Name() {
Expand Down
98 changes: 98 additions & 0 deletions codegen/config/binder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package config

import (
"fmt"
"go/types"
"regexp"
"strings"

"github.com/pkg/errors"
"golang.org/x/tools/go/loader"
)

// Binder connects graphql types to golang types using static analysis
type Binder struct {
program *loader.Program
types TypeMap
}

func (c *Config) NewBinder() (*Binder, error) {
conf := loader.Config{
AllowErrors: true,
TypeChecker: types.Config{
Error: func(e error) {},
},
}

for _, pkg := range c.Models.ReferencedPackages() {
conf.Import(pkg)
}

prog, err := conf.Load()
if err != nil {
return nil, errors.Wrap(err, "loading program")
}

return &Binder{
program: prog,
types: c.Models,
}, nil
}

func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
obj, err := b.FindObject(pkgName, typeName)
if err != nil {
return nil, err
}

if fun, isFunc := obj.(*types.Func); isFunc {
return fun.Type().(*types.Signature).Params().At(0).Type(), nil
}
return obj.Type(), nil
}

func (b *Binder) getPkg(find string) *loader.PackageInfo {
for n, p := range b.program.Imported {
if normalizeVendor(find) == normalizeVendor(n) {
return p
}
}
return nil
}

func (b *Binder) FindObject(pkgName string, typeName string) (types.Object, error) {
if pkgName == "" {
return nil, fmt.Errorf("package cannot be nil")
}
fullName := typeName
if pkgName != "" {
fullName = pkgName + "." + typeName
}

pkg := b.getPkg(pkgName)
if pkg == nil {
return nil, errors.Errorf("required package was not loaded: %s", fullName)
}

for astNode, def := range pkg.Defs {
// only look at defs in the top scope
if def == nil || def.Parent() == nil || def.Parent() != pkg.Pkg.Scope() {
continue
}

if astNode.Name == typeName || astNode.Name == "Marshal"+typeName {
return def, nil
}
}

return nil, errors.Errorf("unable to find type %s\n", fullName)
}

var modsRegex = regexp.MustCompile(`^(\*|\[\])*`)

func normalizeVendor(pkg string) string {
modifiers := modsRegex.FindAllString(pkg, 1)[0]
pkg = strings.TrimPrefix(pkg, modifiers)
parts := strings.Split(pkg, "/vendor/")
return modifiers + parts[len(parts)-1]
}
15 changes: 15 additions & 0 deletions codegen/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ func (tm TypeMap) ReferencedPackages() []string {
return pkgs
}

func (tm TypeMap) Add(gqlName string, goType string) {
modelCfg := tm[gqlName]
modelCfg.Model = goType
tm[gqlName] = modelCfg
}

func inStrSlice(haystack []string, needle string) bool {
for _, v := range haystack {
if needle == v {
Expand Down Expand Up @@ -325,6 +331,15 @@ func (c *Config) normalize() error {
return nil
}

func (c *TypeMapEntry) PkgAndType() (string, string) {
parts := strings.Split(c.Model, ".")
if len(parts) == 1 {
return "", c.Model
}

return normalizeVendor(strings.Join(parts[:len(parts)-1], ".")), parts[len(parts)-1]
}

func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) {
schemaStrings := map[string]string{}

Expand Down
25 changes: 0 additions & 25 deletions codegen/config/loader.go

This file was deleted.

Loading

0 comments on commit 62175ea

Please sign in to comment.