codegen.go

  1package codegen
  2
  3import (
  4	"log"
  5	"os"
  6	"path/filepath"
  7	"regexp"
  8	"syscall"
  9
 10	"github.com/99designs/gqlgen/codegen/templates"
 11	"github.com/pkg/errors"
 12	"github.com/vektah/gqlparser"
 13	"github.com/vektah/gqlparser/ast"
 14	"github.com/vektah/gqlparser/gqlerror"
 15)
 16
 17func Generate(cfg Config) error {
 18	if err := cfg.normalize(); err != nil {
 19		return err
 20	}
 21
 22	_ = syscall.Unlink(cfg.Exec.Filename)
 23	_ = syscall.Unlink(cfg.Model.Filename)
 24
 25	modelsBuild, err := cfg.models()
 26	if err != nil {
 27		return errors.Wrap(err, "model plan failed")
 28	}
 29	if len(modelsBuild.Models) > 0 || len(modelsBuild.Enums) > 0 {
 30		if err = templates.RenderToFile("models.gotpl", cfg.Model.Filename, modelsBuild); err != nil {
 31			return err
 32		}
 33
 34		for _, model := range modelsBuild.Models {
 35			modelCfg := cfg.Models[model.GQLType]
 36			modelCfg.Model = cfg.Model.ImportPath() + "." + model.GoType
 37			cfg.Models[model.GQLType] = modelCfg
 38		}
 39
 40		for _, enum := range modelsBuild.Enums {
 41			modelCfg := cfg.Models[enum.GQLType]
 42			modelCfg.Model = cfg.Model.ImportPath() + "." + enum.GoType
 43			cfg.Models[enum.GQLType] = modelCfg
 44		}
 45	}
 46
 47	build, err := cfg.bind()
 48	if err != nil {
 49		return errors.Wrap(err, "exec plan failed")
 50	}
 51
 52	if err := templates.RenderToFile("generated.gotpl", cfg.Exec.Filename, build); err != nil {
 53		return err
 54	}
 55
 56	if cfg.Resolver.IsDefined() {
 57		if err := generateResolver(cfg); err != nil {
 58			return errors.Wrap(err, "generating resolver failed")
 59		}
 60	}
 61
 62	if err := cfg.validate(); err != nil {
 63		return errors.Wrap(err, "validation failed")
 64	}
 65
 66	return nil
 67}
 68
 69func GenerateServer(cfg Config, filename string) error {
 70	if err := cfg.Exec.normalize(); err != nil {
 71		return errors.Wrap(err, "exec")
 72	}
 73	if err := cfg.Resolver.normalize(); err != nil {
 74		return errors.Wrap(err, "resolver")
 75	}
 76
 77	serverFilename := abs(filename)
 78	serverBuild := cfg.server(filepath.Dir(serverFilename))
 79
 80	if _, err := os.Stat(serverFilename); os.IsNotExist(errors.Cause(err)) {
 81		err = templates.RenderToFile("server.gotpl", serverFilename, serverBuild)
 82		if err != nil {
 83			return errors.Wrap(err, "generate server failed")
 84		}
 85	} else {
 86		log.Printf("Skipped server: %s already exists\n", serverFilename)
 87	}
 88	return nil
 89}
 90
 91func generateResolver(cfg Config) error {
 92	resolverBuild, err := cfg.resolver()
 93	if err != nil {
 94		return errors.Wrap(err, "resolver build failed")
 95	}
 96	filename := cfg.Resolver.Filename
 97
 98	if resolverBuild.ResolverFound {
 99		log.Printf("Skipped resolver: %s.%s already exists\n", cfg.Resolver.ImportPath(), cfg.Resolver.Type)
100		return nil
101	}
102
103	if _, err := os.Stat(filename); os.IsNotExist(errors.Cause(err)) {
104		if err := templates.RenderToFile("resolver.gotpl", filename, resolverBuild); err != nil {
105			return err
106		}
107	} else {
108		log.Printf("Skipped resolver: %s already exists\n", filename)
109	}
110
111	return nil
112}
113
114func (cfg *Config) normalize() error {
115	if err := cfg.Model.normalize(); err != nil {
116		return errors.Wrap(err, "model")
117	}
118
119	if err := cfg.Exec.normalize(); err != nil {
120		return errors.Wrap(err, "exec")
121	}
122
123	if cfg.Resolver.IsDefined() {
124		if err := cfg.Resolver.normalize(); err != nil {
125			return errors.Wrap(err, "resolver")
126		}
127	}
128
129	builtins := TypeMap{
130		"__Directive":  {Model: "github.com/99designs/gqlgen/graphql/introspection.Directive"},
131		"__Type":       {Model: "github.com/99designs/gqlgen/graphql/introspection.Type"},
132		"__Field":      {Model: "github.com/99designs/gqlgen/graphql/introspection.Field"},
133		"__EnumValue":  {Model: "github.com/99designs/gqlgen/graphql/introspection.EnumValue"},
134		"__InputValue": {Model: "github.com/99designs/gqlgen/graphql/introspection.InputValue"},
135		"__Schema":     {Model: "github.com/99designs/gqlgen/graphql/introspection.Schema"},
136		"Int":          {Model: "github.com/99designs/gqlgen/graphql.Int"},
137		"Float":        {Model: "github.com/99designs/gqlgen/graphql.Float"},
138		"String":       {Model: "github.com/99designs/gqlgen/graphql.String"},
139		"Boolean":      {Model: "github.com/99designs/gqlgen/graphql.Boolean"},
140		"ID":           {Model: "github.com/99designs/gqlgen/graphql.ID"},
141		"Time":         {Model: "github.com/99designs/gqlgen/graphql.Time"},
142		"Map":          {Model: "github.com/99designs/gqlgen/graphql.Map"},
143	}
144
145	if cfg.Models == nil {
146		cfg.Models = TypeMap{}
147	}
148	for typeName, entry := range builtins {
149		if !cfg.Models.Exists(typeName) {
150			cfg.Models[typeName] = entry
151		}
152	}
153
154	var sources []*ast.Source
155	for _, filename := range cfg.SchemaFilename {
156		sources = append(sources, &ast.Source{Name: filename, Input: cfg.SchemaStr[filename]})
157	}
158
159	var err *gqlerror.Error
160	cfg.schema, err = gqlparser.LoadSchema(sources...)
161	if err != nil {
162		return err
163	}
164	return nil
165}
166
167var invalidPackageNameChar = regexp.MustCompile(`[^\w]`)
168
169func sanitizePackageName(pkg string) string {
170	return invalidPackageNameChar.ReplaceAllLiteralString(filepath.Base(pkg), "_")
171}
172
173func abs(path string) string {
174	absPath, err := filepath.Abs(path)
175	if err != nil {
176		panic(err)
177	}
178	return filepath.ToSlash(absPath)
179}