diff --git a/cmd/gen.go b/cmd/gen.go index 7d328861e6..182231d625 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -36,16 +36,10 @@ var genCmd = cli.Command{ } } - gen, err := codegen.New(cfg) + err = codegen.Generate(cfg) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(3) } - - err = gen.Generate() - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(4) - } }, } diff --git a/cmd/init.go b/cmd/init.go index cdb9e60ec8..992a37b168 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -70,17 +70,12 @@ var initCmd = cli.Command{ } func GenerateGraphServer(cfg *config.Config, serverFilename string) { - gen, err := codegen.New(cfg) + err := codegen.Generate(cfg) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) } - if err := gen.Generate(); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - - if err := gen.GenerateServer(serverFilename); err != nil { + if err := codegen.GenerateServer(serverFilename, cfg); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } diff --git a/codegen/build.go b/codegen/build.go deleted file mode 100644 index 3bfddba700..0000000000 --- a/codegen/build.go +++ /dev/null @@ -1,210 +0,0 @@ -package codegen - -import ( - "fmt" - "go/build" - "go/types" - "os" - - "github.com/99designs/gqlgen/codegen/config" - "github.com/pkg/errors" - "golang.org/x/tools/go/loader" -) - -type Build struct { - PackageName string - Objects Objects - Inputs Objects - Interfaces []*Interface - QueryRoot *Object - MutationRoot *Object - SubscriptionRoot *Object - SchemaRaw map[string]string - SchemaFilename config.SchemaFilenames - Directives map[string]*Directive -} - -type ModelBuild struct { - PackageName string - Models []Model - Enums []Enum -} - -type ResolverBuild struct { - PackageName string - ResolverType string - Objects Objects - ResolverFound bool -} - -type ServerBuild struct { - PackageName string - ExecPackageName string - ResolverPackageName string -} - -// Create a list of models that need to be generated -func (g *Generator) models() (*ModelBuild, error) { - progLoader := g.newLoaderWithoutErrors() - - prog, err := progLoader.Load() - if err != nil { - return nil, errors.Wrap(err, "loading failed") - } - - namedTypes, err := g.buildNamedTypes(prog) - if err != nil { - return nil, errors.Wrap(err, "binding types failed") - } - - directives, err := g.buildDirectives(namedTypes) - if err != nil { - return nil, err - } - g.Directives = directives - - models, err := g.buildModels(namedTypes, prog) - if err != nil { - return nil, err - } - return &ModelBuild{ - PackageName: g.Model.Package, - Models: models, - Enums: g.buildEnums(namedTypes), - }, nil -} - -// bind a schema together with some code to generate a Build -func (g *Generator) resolver() (*ResolverBuild, error) { - progLoader := g.newLoaderWithoutErrors() - progLoader.Import(g.Resolver.ImportPath()) - - prog, err := progLoader.Load() - if err != nil { - return nil, err - } - - namedTypes, err := g.buildNamedTypes(prog) - if err != nil { - return nil, errors.Wrap(err, "binding types failed") - } - - directives, err := g.buildDirectives(namedTypes) - if err != nil { - return nil, err - } - g.Directives = directives - - objects, err := g.buildObjects(namedTypes, prog) - if err != nil { - return nil, err - } - - def, _ := findGoType(prog, g.Resolver.ImportPath(), g.Resolver.Type) - resolverFound := def != nil - - return &ResolverBuild{ - PackageName: g.Resolver.Package, - Objects: objects, - ResolverType: g.Resolver.Type, - ResolverFound: resolverFound, - }, nil -} - -func (g *Generator) server(destDir string) *ServerBuild { - return &ServerBuild{ - PackageName: g.Resolver.Package, - ExecPackageName: g.Exec.ImportPath(), - ResolverPackageName: g.Resolver.ImportPath(), - } -} - -// bind a schema together with some code to generate a Build -func (g *Generator) bind() (*Build, error) { - progLoader := g.newLoaderWithoutErrors() - prog, err := progLoader.Load() - if err != nil { - return nil, errors.Wrap(err, "loading failed") - } - - namedTypes, err := g.buildNamedTypes(prog) - if err != nil { - return nil, errors.Wrap(err, "binding types failed") - } - - directives, err := g.buildDirectives(namedTypes) - if err != nil { - return nil, err - } - g.Directives = directives - - objects, err := g.buildObjects(namedTypes, prog) - if err != nil { - return nil, err - } - - inputs, err := g.buildInputs(namedTypes, prog) - if err != nil { - return nil, err - } - - b := &Build{ - PackageName: g.Exec.Package, - Objects: objects, - Interfaces: g.buildInterfaces(namedTypes, prog), - Inputs: inputs, - SchemaRaw: g.SchemaStr, - SchemaFilename: g.SchemaFilename, - Directives: directives, - } - - if g.schema.Query != nil { - b.QueryRoot = b.Objects.ByName(g.schema.Query.Name) - } else { - return b, fmt.Errorf("query entry point missing") - } - - if g.schema.Mutation != nil { - b.MutationRoot = b.Objects.ByName(g.schema.Mutation.Name) - } - - if g.schema.Subscription != nil { - b.SubscriptionRoot = b.Objects.ByName(g.schema.Subscription.Name) - } - return b, nil -} - -func (g *Generator) validate() error { - progLoader := g.newLoaderWithErrors() - _, err := progLoader.Load() - return err -} - -func (g *Generator) newLoaderWithErrors() loader.Config { - conf := loader.Config{} - - for _, pkg := range g.Models.ReferencedPackages() { - conf.Import(pkg) - } - return conf -} - -func (g *Generator) newLoaderWithoutErrors() loader.Config { - conf := g.newLoaderWithErrors() - conf.AllowErrors = true - conf.TypeChecker = types.Config{ - Error: func(e error) {}, - } - return conf -} - -func resolvePkg(pkgName string) (string, error) { - cwd, _ := os.Getwd() - - pkg, err := build.Default.Import(pkgName, cwd, build.FindOnly) - if err != nil { - return "", err - } - - return pkg.ImportPath, nil -} diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go index abe0e39c25..3045f286b2 100644 --- a/codegen/codegen_test.go +++ b/codegen/codegen_test.go @@ -1,68 +1,31 @@ package codegen import ( - "syscall" "testing" "github.com/99designs/gqlgen/codegen/config" "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" "golang.org/x/tools/go/loader" ) func TestGenerateServer(t *testing.T) { name := "graphserver" - schema := ` - type Query { - user: User - } - type User { - id: Int - fist_name: String - } - enum Status { - OK - ERROR - } -` - serverFilename := "gen/" + name + "/server/server.go" - gen := Generator{ - Config: &config.Config{ - SchemaFilename: config.SchemaFilenames{"schema.graphql"}, - Exec: config.PackageConfig{Filename: "gen/" + name + "/exec.go"}, - Model: config.PackageConfig{Filename: "gen/" + name + "/model.go"}, - Resolver: config.PackageConfig{Filename: "gen/" + name + "/resolver.go", Type: "Resolver"}, - }, - SchemaStr: map[string]string{"schema.graphql": schema}, + cfg := &config.Config{ + SchemaFilename: config.SchemaFilenames{"testdata/generateserver.graphqls"}, + Exec: config.PackageConfig{Filename: "gen/" + name + "/exec.go"}, + Model: config.PackageConfig{Filename: "gen/" + name + "/model.go"}, + Resolver: config.PackageConfig{Filename: "gen/" + name + "/resolver.go", Type: "Resolver"}, } + serverFilename := "gen/" + name + "/server/server.go" - err := gen.Config.Check() - if err != nil { - panic(err) - } - - var gerr *gqlerror.Error - gen.schema, gerr = gqlparser.LoadSchema(&ast.Source{Name: "schema.graphql", Input: schema}) - if gerr != nil { - panic(gerr) - } - - _ = syscall.Unlink(gen.Resolver.Filename) - _ = syscall.Unlink(serverFilename) - - err = gen.Generate() - require.NoError(t, err) - - err = gen.GenerateServer(serverFilename) - require.NoError(t, err) + require.NoError(t, Generate(cfg)) + require.NoError(t, GenerateServer(serverFilename, cfg)) conf := loader.Config{} conf.CreateFromFilenames("gen/"+name, serverFilename) - _, err = conf.Load() + _, err := conf.Load() require.NoError(t, err) t.Run("list of enums", func(t *testing.T) { diff --git a/codegen/config/config.go b/codegen/config/config.go index bec8e00fb2..fb6c9522a8 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -178,23 +178,23 @@ func (c *PackageConfig) IsDefined() bool { return c.Filename != "" } -func (cfg *Config) Check() error { - if err := cfg.Models.Check(); err != nil { +func (c *Config) Check() error { + if err := c.Models.Check(); err != nil { return errors.Wrap(err, "config.models") } - if err := cfg.Exec.Check(); err != nil { + if err := c.Exec.Check(); err != nil { return errors.Wrap(err, "config.exec") } - if err := cfg.Model.Check(); err != nil { + if err := c.Model.Check(); err != nil { return errors.Wrap(err, "config.model") } - if cfg.Resolver.IsDefined() { - if err := cfg.Resolver.Check(); err != nil { + if c.Resolver.IsDefined() { + if err := c.Resolver.Check(); err != nil { return errors.Wrap(err, "config.resolver") } } - return cfg.normalize() + return c.normalize() } type TypeMap map[string]TypeMapEntry @@ -280,17 +280,17 @@ func findCfgInDir(dir string) string { return "" } -func (cfg *Config) normalize() error { - if err := cfg.Model.normalize(); err != nil { +func (c *Config) normalize() error { + if err := c.Model.normalize(); err != nil { return errors.Wrap(err, "model") } - if err := cfg.Exec.normalize(); err != nil { + if err := c.Exec.normalize(); err != nil { return errors.Wrap(err, "exec") } - if cfg.Resolver.IsDefined() { - if err := cfg.Resolver.normalize(); err != nil { + if c.Resolver.IsDefined() { + if err := c.Resolver.normalize(); err != nil { return errors.Wrap(err, "resolver") } } @@ -313,12 +313,12 @@ func (cfg *Config) normalize() error { "Map": {Model: "github.com/99designs/gqlgen/graphql.Map"}, } - if cfg.Models == nil { - cfg.Models = TypeMap{} + if c.Models == nil { + c.Models = TypeMap{} } for typeName, entry := range builtins { - if !cfg.Models.Exists(typeName) { - cfg.Models[typeName] = entry + if !c.Models.Exists(typeName) { + c.Models[typeName] = entry } } diff --git a/codegen/config/loader.go b/codegen/config/loader.go new file mode 100644 index 0000000000..2fa9013cea --- /dev/null +++ b/codegen/config/loader.go @@ -0,0 +1,25 @@ +package config + +import ( + "go/types" + + "golang.org/x/tools/go/loader" +) + +func (c *Config) NewLoaderWithErrors() loader.Config { + conf := loader.Config{} + + for _, pkg := range c.Models.ReferencedPackages() { + conf.Import(pkg) + } + return conf +} + +func (c *Config) NewLoaderWithoutErrors() loader.Config { + conf := c.NewLoaderWithErrors() + conf.AllowErrors = true + conf.TypeChecker = types.Config{ + Error: func(e error) {}, + } + return conf +} diff --git a/codegen/enum.go b/codegen/enum.go deleted file mode 100644 index f062abbdb1..0000000000 --- a/codegen/enum.go +++ /dev/null @@ -1,12 +0,0 @@ -package codegen - -type Enum struct { - Definition *TypeDefinition - Description string - Values []EnumValue -} - -type EnumValue struct { - Name string - Description string -} diff --git a/codegen/enum_build.go b/codegen/enum_build.go deleted file mode 100644 index c476ed01b7..0000000000 --- a/codegen/enum_build.go +++ /dev/null @@ -1,42 +0,0 @@ -package codegen - -import ( - "go/types" - "sort" - "strings" - - "github.com/99designs/gqlgen/codegen/templates" - "github.com/vektah/gqlparser/ast" -) - -func (g *Generator) buildEnums(ts NamedTypes) []Enum { - var enums []Enum - - for _, typ := range g.schema.Types { - namedType := ts[typ.Name] - if typ.Kind != ast.Enum || strings.HasPrefix(typ.Name, "__") || g.Models.UserDefined(typ.Name) { - continue - } - - var values []EnumValue - for _, v := range typ.EnumValues { - values = append(values, EnumValue{v.Name, v.Description}) - } - - enum := Enum{ - Definition: namedType, - Values: values, - Description: typ.Description, - } - - enum.Definition.GoType = types.NewNamed(types.NewTypeName(0, g.Config.Model.Pkg(), templates.ToCamel(enum.Definition.GQLDefinition.Name), nil), nil, nil) - - enums = append(enums, enum) - } - - sort.Slice(enums, func(i, j int) bool { - return enums[i].Definition.GQLDefinition.Name < enums[j].Definition.GQLDefinition.Name - }) - - return enums -} diff --git a/codegen/exec.go b/codegen/exec.go new file mode 100644 index 0000000000..34f5cbd9cd --- /dev/null +++ b/codegen/exec.go @@ -0,0 +1,42 @@ +package codegen + +import ( + "fmt" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/codegen/unified" +) + +type ExecBuild struct { + *unified.Schema + + PackageName string + QueryRoot *unified.Object + MutationRoot *unified.Object + SubscriptionRoot *unified.Object +} + +// bind a schema together with some code to generate a Build +func buildExec(s *unified.Schema) error { + b := &ExecBuild{ + Schema: s, + PackageName: s.Config.Exec.Package, + } + + if s.Schema.Query != nil { + b.QueryRoot = b.Objects.ByName(s.Schema.Query.Name) + } else { + return fmt.Errorf("query entry point missing") + } + + if s.Schema.Mutation != nil { + b.MutationRoot = b.Objects.ByName(s.Schema.Mutation.Name) + } + + if s.Schema.Subscription != nil { + b.SubscriptionRoot = b.Objects.ByName(s.Schema.Subscription.Name) + } + + return templates.RenderToFile("generated.gotpl", s.Config.Exec.Filename, b) + +} diff --git a/codegen/generate.go b/codegen/generate.go new file mode 100644 index 0000000000..36b8e211d6 --- /dev/null +++ b/codegen/generate.go @@ -0,0 +1,60 @@ +package codegen + +import ( + "path/filepath" + "syscall" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/unified" + "github.com/pkg/errors" +) + +func Generate(cfg *config.Config) error { + _ = syscall.Unlink(cfg.Exec.Filename) + _ = syscall.Unlink(cfg.Model.Filename) + + schema, err := unified.NewSchema(cfg) + if err != nil { + return errors.Wrap(err, "merging failed") + } + + if err := buildModels(schema); err != nil { + return errors.Wrap(err, "generating models failed") + } + + // Create a new schema now that the generated models have been injected into the typemap + schema, err = unified.NewSchema(schema.Config) + if err != nil { + return errors.Wrap(err, "merging failed") + } + + if err := buildExec(schema); err != nil { + return errors.Wrap(err, "generating exec failed") + } + + if cfg.Resolver.IsDefined() { + if err := GenerateResolver(schema); err != nil { + return errors.Wrap(err, "generating resolver failed") + } + } + + if err := validate(cfg); err != nil { + return errors.Wrap(err, "validation failed") + } + + return nil +} + +func validate(cfg *config.Config) error { + progLoader := cfg.NewLoaderWithErrors() + _, err := progLoader.Load() + return err +} + +func abs(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + panic(err) + } + return filepath.ToSlash(absPath) +} diff --git a/codegen/generator.go b/codegen/generator.go deleted file mode 100644 index a85a5ff8fe..0000000000 --- a/codegen/generator.go +++ /dev/null @@ -1,132 +0,0 @@ -package codegen - -import ( - "go/types" - "log" - "os" - "path/filepath" - "syscall" - - "github.com/99designs/gqlgen/codegen/config" - "github.com/99designs/gqlgen/codegen/templates" - "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" -) - -type Generator struct { - *config.Config - schema *ast.Schema `yaml:"-"` - SchemaStr map[string]string `yaml:"-"` - Directives map[string]*Directive -} - -func New(cfg *config.Config) (*Generator, error) { - g := &Generator{Config: cfg} - - var err error - g.schema, g.SchemaStr, err = cfg.LoadSchema() - if err != nil { - return nil, err - } - - err = cfg.Check() - if err != nil { - return nil, err - } - - return g, nil -} - -func (g *Generator) Generate() error { - _ = syscall.Unlink(g.Exec.Filename) - _ = syscall.Unlink(g.Model.Filename) - - modelsBuild, err := g.models() - if err != nil { - return errors.Wrap(err, "model plan failed") - } - if len(modelsBuild.Models) > 0 || len(modelsBuild.Enums) > 0 { - if err = templates.RenderToFile("models.gotpl", g.Model.Filename, modelsBuild); err != nil { - return err - } - - for _, model := range modelsBuild.Models { - modelCfg := g.Models[model.Definition.GQLDefinition.Name] - modelCfg.Model = types.TypeString(model.Definition.GoType, nil) - g.Models[model.Definition.GQLDefinition.Name] = modelCfg - } - - for _, enum := range modelsBuild.Enums { - modelCfg := g.Models[enum.Definition.GQLDefinition.Name] - modelCfg.Model = types.TypeString(enum.Definition.GoType, nil) - g.Models[enum.Definition.GQLDefinition.Name] = modelCfg - } - } - - build, err := g.bind() - if err != nil { - return errors.Wrap(err, "exec plan failed") - } - - if err := templates.RenderToFile("generated.gotpl", g.Exec.Filename, build); err != nil { - return err - } - - if g.Resolver.IsDefined() { - if err := g.GenerateResolver(); err != nil { - return errors.Wrap(err, "generating resolver failed") - } - } - - if err := g.validate(); err != nil { - return errors.Wrap(err, "validation failed") - } - - return nil -} - -func (g *Generator) GenerateServer(filename string) error { - serverFilename := abs(filename) - serverBuild := g.server(filepath.Dir(serverFilename)) - - if _, err := os.Stat(serverFilename); os.IsNotExist(errors.Cause(err)) { - err = templates.RenderToFile("server.gotpl", serverFilename, serverBuild) - if err != nil { - return errors.Wrap(err, "generate server failed") - } - } else { - log.Printf("Skipped server: %s already exists\n", serverFilename) - } - return nil -} - -func (g *Generator) GenerateResolver() error { - resolverBuild, err := g.resolver() - if err != nil { - return errors.Wrap(err, "resolver build failed") - } - filename := g.Resolver.Filename - - if resolverBuild.ResolverFound { - log.Printf("Skipped resolver: %s.%s already exists\n", g.Resolver.ImportPath(), g.Resolver.Type) - return nil - } - - if _, err := os.Stat(filename); os.IsNotExist(errors.Cause(err)) { - if err := templates.RenderToFile("resolver.gotpl", filename, resolverBuild); err != nil { - return err - } - } else { - log.Printf("Skipped resolver: %s already exists\n", filename) - } - - return nil -} - -func abs(path string) string { - absPath, err := filepath.Abs(path) - if err != nil { - panic(err) - } - return filepath.ToSlash(absPath) -} diff --git a/codegen/input_build.go b/codegen/input_build.go deleted file mode 100644 index 519e337303..0000000000 --- a/codegen/input_build.go +++ /dev/null @@ -1,90 +0,0 @@ -package codegen - -import ( - "sort" - - "go/types" - - "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/loader" -) - -func (g *Generator) buildInputs(namedTypes NamedTypes, prog *loader.Program) (Objects, error) { - var inputs Objects - - for _, typ := range g.schema.Types { - switch typ.Kind { - case ast.InputObject: - input, err := g.buildInput(namedTypes, typ) - if err != nil { - return nil, err - } - - if _, isMap := input.Definition.GoType.(*types.Map); !isMap { - bindErrs := bindObject(input, g.StructTag) - if len(bindErrs) > 0 { - return nil, bindErrs - } - } - - inputs = append(inputs, input) - } - } - - sort.Slice(inputs, func(i, j int) bool { - return inputs[i].Definition.GQLDefinition.Name < inputs[j].Definition.GQLDefinition.Name - }) - - return inputs, nil -} - -func (g *Generator) buildInput(types NamedTypes, typ *ast.Definition) (*Object, error) { - obj := &Object{Definition: types[typ.Name]} - typeEntry, entryExists := g.Models[typ.Name] - - for _, field := range typ.Fields { - dirs, err := g.getDirectives(field.Directives) - if err != nil { - return nil, err - } - newField := Field{ - GQLName: field.Name, - TypeReference: types.getType(field.Type), - Object: obj, - Directives: dirs, - } - - if entryExists { - if typeField, ok := typeEntry.Fields[field.Name]; ok { - newField.GoFieldName = typeField.FieldName - } - } - - if field.DefaultValue != nil { - var err error - newField.Default, err = field.DefaultValue.Value(nil) - if err != nil { - return nil, errors.Errorf("default value for %s.%s is not valid: %s", typ.Name, field.Name, err.Error()) - } - } - - if !newField.TypeReference.Definition.GQLDefinition.IsInputType() { - return nil, errors.Errorf( - "%s cannot be used as a field of %s. only input and scalar types are allowed", - newField.Definition.GQLDefinition.Name, - obj.Definition.GQLDefinition.Name, - ) - } - - obj.Fields = append(obj.Fields, newField) - - } - dirs, err := g.getDirectives(typ.Directives) - if err != nil { - return nil, err - } - obj.Directives = dirs - - return obj, nil -} diff --git a/codegen/input_test.go b/codegen/input_test.go deleted file mode 100644 index 744cf9c8ad..0000000000 --- a/codegen/input_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package codegen - -import ( - "testing" - - "github.com/99designs/gqlgen/codegen/config" - "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "golang.org/x/tools/go/loader" -) - -func TestTypeUnionAsInput(t *testing.T) { - err := generate("inputunion", ` - type Query { - addBookmark(b: Bookmarkable!): Boolean! - } - type Item {name: String} - union Bookmarkable = Item - `) - - require.EqualError(t, err, "model plan failed: Bookmarkable! cannot be used as argument of Query.addBookmark. only input and scalar types are allowed") -} - -func TestTypeInInput(t *testing.T) { - err := generate("typeinput", ` - type Query { - addBookmark(b: BookmarkableInput!): Boolean! - } - type Item {name: String} - input BookmarkableInput { - item: Item - } - `) - - require.EqualError(t, err, "model plan failed: Item cannot be used as a field of BookmarkableInput. only input and scalar types are allowed") -} - -func generate(name string, schema string, typemap ...config.TypeMap) error { - gen := Generator{ - Config: &config.Config{ - SchemaFilename: config.SchemaFilenames{"schema.graphql"}, - Exec: config.PackageConfig{Filename: "gen/" + name + "/exec.go"}, - Model: config.PackageConfig{Filename: "gen/" + name + "/model.go"}, - }, - - SchemaStr: map[string]string{"schema.graphql": schema}, - } - - err := gen.Config.Check() - if err != nil { - panic(err) - } - - var gerr *gqlerror.Error - gen.schema, gerr = gqlparser.LoadSchema(&ast.Source{Name: "schema.graphql", Input: schema}) - if gerr != nil { - panic(gerr) - } - - if len(typemap) > 0 { - gen.Models = typemap[0] - } - err = gen.Generate() - if err != nil { - return err - } - conf := loader.Config{} - conf.Import("github.com/99designs/gqlgen/codegen/testdata/gen/" + name) - - _, err = conf.Load() - if err != nil { - panic(err) - } - return nil -} diff --git a/codegen/interface_build.go b/codegen/interface_build.go deleted file mode 100644 index 1cc63229ba..0000000000 --- a/codegen/interface_build.go +++ /dev/null @@ -1,53 +0,0 @@ -package codegen - -import ( - "go/types" - "sort" - - "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/loader" -) - -func (g *Generator) buildInterfaces(types NamedTypes, prog *loader.Program) []*Interface { - var interfaces []*Interface - for _, typ := range g.schema.Types { - if typ.Kind == ast.Union || typ.Kind == ast.Interface { - interfaces = append(interfaces, g.buildInterface(types, typ, prog)) - } - } - - sort.Slice(interfaces, func(i, j int) bool { - return interfaces[i].Definition.GQLDefinition.Name < interfaces[j].Definition.GQLDefinition.Name - }) - - return interfaces -} - -func (g *Generator) buildInterface(types NamedTypes, typ *ast.Definition, prog *loader.Program) *Interface { - i := &Interface{Definition: types[typ.Name]} - - for _, implementor := range g.schema.GetPossibleTypes(typ) { - t := types[implementor.Name] - - i.Implementors = append(i.Implementors, InterfaceImplementor{ - Definition: t, - ValueReceiver: g.isValueReceiver(types[typ.Name], t, prog), - }) - } - - return i -} - -func (g *Generator) isValueReceiver(intf *TypeDefinition, implementor *TypeDefinition, prog *loader.Program) bool { - interfaceType, err := findGoInterface(intf.GoType) - if interfaceType == nil || err != nil { - return true - } - - implementorType, err := findGoNamedType(implementor.GoType) - if implementorType == nil || err != nil { - return true - } - - return types.Implements(implementorType, interfaceType) -} diff --git a/codegen/model.go b/codegen/model.go index 09b2b4c8cf..df377b9cfd 100644 --- a/codegen/model.go +++ b/codegen/model.go @@ -1,17 +1,66 @@ package codegen -type Model struct { - Definition *TypeDefinition - Description string - Fields []ModelField - Implements []*TypeDefinition +import ( + "sort" + + "go/types" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/codegen/unified" +) + +type ModelBuild struct { + Interfaces []*unified.Interface + Models unified.Objects + Enums []unified.Enum + PackageName string } -type ModelField struct { - *TypeReference - GQLName string - GoFieldName string - GoFKName string - GoFKType string - Description string +// Create a list of models that need to be generated +func buildModels(s *unified.Schema) error { + b := &ModelBuild{ + PackageName: s.Config.Model.Package, + } + + for _, o := range s.Interfaces { + if !o.InTypemap { + b.Interfaces = append(b.Interfaces, o) + } + } + + for _, o := range append(s.Objects, s.Inputs...) { + if !o.InTypemap { + b.Models = append(b.Models, o) + } + } + + for _, e := range s.Enums { + if !e.InTypemap { + b.Enums = append(b.Enums, e) + } + } + + if len(b.Models) == 0 && len(b.Enums) == 0 { + return nil + } + + sort.Slice(b.Models, func(i, j int) bool { + return b.Models[i].Definition.GQLDefinition.Name < b.Models[j].Definition.GQLDefinition.Name + }) + + err := templates.RenderToFile("models.gotpl", s.Config.Model.Filename, b) + + for _, model := range b.Models { + modelCfg := s.Config.Models[model.Definition.GQLDefinition.Name] + modelCfg.Model = types.TypeString(model.Definition.GoType, nil) + s.Config.Models[model.Definition.GQLDefinition.Name] = modelCfg + } + + for _, enum := range b.Enums { + modelCfg := s.Config.Models[enum.Definition.GQLDefinition.Name] + modelCfg.Model = types.TypeString(enum.Definition.GoType, nil) + s.Config.Models[enum.Definition.GQLDefinition.Name] = modelCfg + } + + return err } diff --git a/codegen/models_build.go b/codegen/models_build.go deleted file mode 100644 index 5eb707c7d4..0000000000 --- a/codegen/models_build.go +++ /dev/null @@ -1,82 +0,0 @@ -package codegen - -import ( - "sort" - - "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/loader" -) - -func (g *Generator) buildModels(types NamedTypes, prog *loader.Program) ([]Model, error) { - var models []Model - - for _, typ := range g.schema.Types { - var model Model - if g.Models.UserDefined(typ.Name) { - continue - } - switch typ.Kind { - case ast.Object: - obj, err := g.buildObject(prog, types, typ) - if err != nil { - return nil, err - } - if obj.Root { - continue - } - model = g.obj2Model(obj) - case ast.InputObject: - obj, err := g.buildInput(types, typ) - if err != nil { - return nil, err - } - model = g.obj2Model(obj) - case ast.Interface, ast.Union: - intf := g.buildInterface(types, typ, prog) - model = int2Model(intf) - default: - continue - } - model.Description = typ.Description // It's this or change both obj2Model and buildObject - - models = append(models, model) - } - - sort.Slice(models, func(i, j int) bool { - return models[i].Definition.GQLDefinition.Name < models[j].Definition.GQLDefinition.Name - }) - - return models, nil -} - -func (g *Generator) obj2Model(obj *Object) Model { - model := Model{ - Definition: obj.Definition, - Implements: obj.Implements, - Fields: []ModelField{}, - } - - for i := range obj.Fields { - field := &obj.Fields[i] - mf := ModelField{TypeReference: field.TypeReference, GQLName: field.GQLName} - - if field.GoFieldName != "" { - mf.GoFieldName = field.GoFieldName - } else { - mf.GoFieldName = field.GoNameExported() - } - - model.Fields = append(model.Fields, mf) - } - - return model -} - -func int2Model(obj *Interface) Model { - model := Model{ - Definition: obj.Definition, - Fields: []ModelField{}, - } - - return model -} diff --git a/codegen/object_build.go b/codegen/object_build.go deleted file mode 100644 index 65f9066b9d..0000000000 --- a/codegen/object_build.go +++ /dev/null @@ -1,196 +0,0 @@ -package codegen - -import ( - "sort" - - "go/types" - - "log" - - "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/loader" -) - -func (g *Generator) buildObjects(ts NamedTypes, prog *loader.Program) (Objects, error) { - var objects Objects - - for _, typ := range g.schema.Types { - if typ.Kind != ast.Object { - continue - } - - obj, err := g.buildObject(prog, ts, typ) - if err != nil { - return nil, err - } - - if _, isMap := obj.Definition.GoType.(*types.Map); !isMap { - for _, bindErr := range bindObject(obj, g.StructTag) { - log.Println(bindErr.Error()) - log.Println(" Adding resolver method") - } - } - - objects = append(objects, obj) - } - - sort.Slice(objects, func(i, j int) bool { - return objects[i].Definition.GQLDefinition.Name < objects[j].Definition.GQLDefinition.Name - }) - - return objects, nil -} - -var keywords = []string{ - "break", - "default", - "func", - "interface", - "select", - "case", - "defer", - "go", - "map", - "struct", - "chan", - "else", - "goto", - "package", - "switch", - "const", - "fallthrough", - "if", - "range", - "type", - "continue", - "for", - "import", - "return", - "var", -} - -// sanitizeArgName prevents collisions with go keywords for arguments to resolver functions -func sanitizeArgName(name string) string { - for _, k := range keywords { - if name == k { - return name + "Arg" - } - } - return name -} - -func (g *Generator) buildObject(prog *loader.Program, ts NamedTypes, typ *ast.Definition) (*Object, error) { - obj := &Object{Definition: ts[typ.Name]} - typeEntry, entryExists := g.Models[typ.Name] - - tt := types.NewTypeName(0, g.Config.Exec.Pkg(), obj.Definition.GQLDefinition.Name+"Resolver", nil) - obj.ResolverInterface = types.NewNamed(tt, nil, nil) - - if typ == g.schema.Query { - obj.Root = true - } - - if typ == g.schema.Mutation { - obj.Root = true - obj.DisableConcurrency = true - } - - if typ == g.schema.Subscription { - obj.Root = true - obj.Stream = true - } - - obj.Satisfies = append(obj.Satisfies, typ.Interfaces...) - - for _, intf := range g.schema.GetImplements(typ) { - obj.Implements = append(obj.Implements, ts[intf.Name]) - } - - for _, field := range typ.Fields { - if typ == g.schema.Query && field.Name == "__type" { - schemaType, err := findGoType(prog, "github.com/99designs/gqlgen/graphql/introspection", "Schema") - if err != nil { - return nil, errors.Wrap(err, "unable to find root schema introspection type") - } - - obj.Fields = append(obj.Fields, Field{ - TypeReference: &TypeReference{ts["__Schema"], types.NewPointer(schemaType.Type()), ast.NamedType("__Schema", nil)}, - GQLName: "__schema", - GoFieldType: GoFieldMethod, - GoReceiverName: "ec", - GoFieldName: "introspectSchema", - Object: obj, - Description: field.Description, - }) - continue - } - if typ == g.schema.Query && field.Name == "__schema" { - typeType, err := findGoType(prog, "github.com/99designs/gqlgen/graphql/introspection", "Type") - if err != nil { - return nil, errors.Wrap(err, "unable to find root schema introspection type") - } - - obj.Fields = append(obj.Fields, Field{ - TypeReference: &TypeReference{ts["__Type"], types.NewPointer(typeType.Type()), ast.NamedType("__Schema", nil)}, - GQLName: "__type", - GoFieldType: GoFieldMethod, - GoReceiverName: "ec", - GoFieldName: "introspectType", - Args: []FieldArgument{ - {GQLName: "name", TypeReference: &TypeReference{ts["String"], types.Typ[types.String], ast.NamedType("String", nil)}, Object: &Object{}}, - }, - Object: obj, - }) - continue - } - - var forceResolver bool - var goName string - if entryExists { - if typeField, ok := typeEntry.Fields[field.Name]; ok { - goName = typeField.FieldName - forceResolver = typeField.Resolver - } - } - - var args []FieldArgument - for _, arg := range field.Arguments { - dirs, err := g.getDirectives(arg.Directives) - if err != nil { - return nil, err - } - newArg := FieldArgument{ - GQLName: arg.Name, - TypeReference: ts.getType(arg.Type), - Object: obj, - GoVarName: sanitizeArgName(arg.Name), - Directives: dirs, - } - - if !newArg.TypeReference.Definition.GQLDefinition.IsInputType() { - return nil, errors.Errorf("%s cannot be used as argument of %s.%s. only input and scalar types are allowed", arg.Type, obj.Definition.GQLDefinition.Name, field.Name) - } - - if arg.DefaultValue != nil { - var err error - newArg.Default, err = arg.DefaultValue.Value(nil) - if err != nil { - return nil, errors.Errorf("default value for %s.%s is not valid: %s", typ.Name, field.Name, err.Error()) - } - } - args = append(args, newArg) - } - - obj.Fields = append(obj.Fields, Field{ - GQLName: field.Name, - TypeReference: ts.getType(field.Type), - Args: args, - Object: obj, - GoFieldName: goName, - ForceResolver: forceResolver, - }) - } - - return obj, nil -} diff --git a/codegen/resolver.go b/codegen/resolver.go new file mode 100644 index 0000000000..ae5a2bb122 --- /dev/null +++ b/codegen/resolver.go @@ -0,0 +1,53 @@ +package codegen + +import ( + "log" + "os" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/codegen/unified" + "github.com/pkg/errors" +) + +type ResolverBuild struct { + *unified.Schema + + PackageName string + ResolverType string + ResolverFound bool +} + +func GenerateResolver(schema *unified.Schema) error { + resolverBuild, err := buildResolver(schema) + if err != nil { + return errors.Wrap(err, "resolver build failed") + } + filename := schema.Config.Resolver.Filename + + if resolverBuild.ResolverFound { + log.Printf("Skipped resolver: %s.%s already exists\n", schema.Config.Resolver.ImportPath(), schema.Config.Resolver.Type) + return nil + } + + if _, err := os.Stat(filename); os.IsNotExist(errors.Cause(err)) { + if err := templates.RenderToFile("resolver.gotpl", filename, resolverBuild); err != nil { + return err + } + } else { + log.Printf("Skipped resolver: %s already exists\n", filename) + } + + return nil +} + +func buildResolver(s *unified.Schema) (*ResolverBuild, error) { + def, _ := s.FindGoType(s.Config.Resolver.ImportPath(), s.Config.Resolver.Type) + resolverFound := def != nil + + return &ResolverBuild{ + Schema: s, + PackageName: s.Config.Resolver.Package, + ResolverType: s.Config.Resolver.Type, + ResolverFound: resolverFound, + }, nil +} diff --git a/codegen/server.go b/codegen/server.go new file mode 100644 index 0000000000..3a42f8baf1 --- /dev/null +++ b/codegen/server.go @@ -0,0 +1,42 @@ +package codegen + +import ( + "log" + "os" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/codegen/unified" + "github.com/pkg/errors" +) + +type ServerBuild struct { + unified.Schema + + PackageName string + ExecPackageName string + ResolverPackageName string +} + +func GenerateServer(filename string, cfg *config.Config) error { + serverBuild := buildServer(cfg) + + serverFilename := abs(filename) + if _, err := os.Stat(serverFilename); os.IsNotExist(errors.Cause(err)) { + err = templates.RenderToFile("server.gotpl", serverFilename, serverBuild) + if err != nil { + return errors.Wrap(err, "generate server failed") + } + } else { + log.Printf("Skipped server: %s already exists\n", serverFilename) + } + return nil +} + +func buildServer(config *config.Config) *ServerBuild { + return &ServerBuild{ + PackageName: config.Resolver.Package, + ExecPackageName: config.Exec.ImportPath(), + ResolverPackageName: config.Resolver.ImportPath(), + } +} diff --git a/codegen/templates/data.go b/codegen/templates/data.go index 0d885d11e3..4e4739df4a 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -3,10 +3,10 @@ package templates var data = map[string]string{ "args.gotpl": "\targs := map[string]interface{}{}\n\t{{- range $i, $arg := . }}\n\t\tvar arg{{$i}} {{$arg.GoType | ref }}\n\t\tif tmp, ok := rawArgs[{{$arg.GQLName|quote}}]; ok {\n\t\t\t{{- if $arg.Directives }}\n\t\t\t\targm{{$i}}, err := chainFieldMiddleware([]graphql.FieldMiddleware{\n\t\t\t\t{{- range $directive := $arg.Directives }}\n\t\t\t\t\tfunc(ctx context.Context, n graphql.Resolver) (res interface{}, err error) {\n\t\t\t\t\t{{- range $dArg := $directive.Args }}\n\t\t\t\t\t\t{{- if and $dArg.IsPtr ( notNil \"Value\" $dArg ) }}\n\t\t\t\t\t\t\t{{ $dArg.GoVarName }} := {{ $dArg.Value | dump }}\n\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t{{- end }}\n\t\t\t\t\t\treturn e.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs \"tmp\" \"n\" }})\n\t\t\t\t\t},\n\t\t\t\t{{- end }}\n\t\t\t\t}...)(ctx, func(ctx2 context.Context) (interface{}, error) {\n\t\t\t\t\tvar err error\n\t\t\t\t\t{{$arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\treturn arg{{ $i }}, nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif data, ok := argm{{$i}}.({{$arg.GoType | ref }}); ok {\n\t\t\t\t\targ{{$i}} = data\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, errors.New(\"expect {{$arg.GoType | ref }}\")\n\t\t\t\t}\n\t\t\t{{- else }}\n\t\t\t\tvar err error\n\t\t\t\t{{ $arg.Unmarshal (print \"arg\" $i) \"tmp\" }}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\t{{- if eq $arg.Definition.GQLDefinition.Kind \"INPUT_OBJECT\" }}\n\t\t\t\t{{ $arg.Middleware (print \"arg\" $i) (print \"arg\" $i) }}\n\t\t\t{{- end }}\n\t\t}\n\t\targs[{{$arg.GQLName|quote}}] = arg{{$i}}\n\t{{- end }}\n\treturn args, nil\n", "field.gotpl": "{{ $field := . }}\n{{ $object := $field.Object }}\n\n{{- if $object.Stream }}\n\tfunc (ec *executionContext) _{{$object.Definition.GQLDefinition.Name}}_{{$field.GQLName}}(ctx context.Context, field graphql.CollectedField) func() graphql.Marshaler {\n\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\t\tField: field,\n\t\t\tArgs: nil,\n\t\t})\n\t\t{{- if $field.Args }}\n\t\t\trawArgs := field.ArgumentMap(ec.Variables)\n\t\t\targs, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs)\n\t\t\tif err != nil {\n\t\t\t\tec.Error(ctx, err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t{{- end }}\n\t\t// FIXME: subscriptions are missing request middleware stack https://github.com/99designs/gqlgen/issues/259\n\t\t// and Tracer stack\n\t\trctx := ctx\n\t\tresults, err := ec.resolvers.{{ $field.ShortInvocation }}\n\t\tif err != nil {\n\t\t\tec.Error(ctx, 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\treturn graphql.WriterFunc(func(w io.Writer) {\n\t\t\t\tw.Write([]byte{'{'})\n\t\t\t\tgraphql.MarshalString(field.Alias).MarshalGQL(w)\n\t\t\t\tw.Write([]byte{':'})\n\t\t\t\tfunc() graphql.Marshaler {\n\t\t\t\t\t{{ $field.WriteJson }}\n\t\t\t\t}().MarshalGQL(w)\n\t\t\t\tw.Write([]byte{'}'})\n\t\t\t})\n\t\t}\n\t}\n{{ else }}\n\t// nolint: vetshadow\n\tfunc (ec *executionContext) _{{$object.Definition.GQLDefinition.Name}}_{{$field.GQLName}}(ctx context.Context, field graphql.CollectedField, {{if not $object.Root}}obj *{{$object.Definition.GoType | ref}}{{end}}) graphql.Marshaler {\n\t\tctx = ec.Tracer.StartFieldExecution(ctx, field)\n\t\tdefer func () { ec.Tracer.EndFieldExecution(ctx) }()\n\t\trctx := &graphql.ResolverContext{\n\t\t\tObject: {{$object.Definition.GQLDefinition.Name|quote}},\n\t\t\tField: field,\n\t\t\tArgs: nil,\n\t\t}\n\t\tctx = graphql.WithResolverContext(ctx, rctx)\n\t\t{{- if $field.Args }}\n\t\t\trawArgs := field.ArgumentMap(ec.Variables)\n\t\t\targs, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs)\n\t\t\tif err != nil {\n\t\t\t\tec.Error(ctx, err)\n\t\t\t\treturn graphql.Null\n\t\t\t}\n\t\t\trctx.Args = args\n\t\t{{- end }}\n\t\tctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)\n\t\tresTmp := ec.FieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {\n\t\t\tctx = rctx // use context from middleware stack in children\n\t\t\t{{- if $field.IsResolver }}\n\t\t\t\treturn ec.resolvers.{{ $field.ShortInvocation }}\n\t\t\t{{- else if $field.IsMethod }}\n\t\t\t\t{{- if $field.NoErr }}\n\t\t\t\t\treturn {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }}), nil\n\t\t\t\t{{- else }}\n\t\t\t\t\treturn {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }})\n\t\t\t\t{{- end }}\n\t\t\t{{- else if $field.IsVariable }}\n\t\t\t\treturn {{$field.GoReceiverName}}.{{$field.GoFieldName}}, nil\n\t\t\t{{- end }}\n\t\t})\n\t\tif resTmp == nil {\n\t\t\t{{- if $field.ASTType.NonNull }}\n\t\t\t\tif !ec.HasError(rctx) {\n\t\t\t\t\tec.Errorf(ctx, \"must not be null\")\n\t\t\t\t}\n\t\t\t{{- end }}\n\t\t\treturn graphql.Null\n\t\t}\n\t\tres := resTmp.({{$field.GoType | ref}})\n\t\trctx.Result = res\n\t\tctx = ec.Tracer.StartFieldChildExecution(ctx)\n\t\t{{ $field.WriteJson }}\n\t}\n{{ end }}\n", - "generated.gotpl": "// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\npackage {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\n// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.\nfunc NewExecutableSchema(cfg Config) graphql.ExecutableSchema {\n\treturn &executableSchema{\n\t\tresolvers: cfg.Resolvers,\n\t\tdirectives: cfg.Directives,\n\t\tcomplexity: cfg.Complexity,\n\t}\n}\n\ntype Config struct {\n\tResolvers ResolverRoot\n\tDirectives DirectiveRoot\n\tComplexity ComplexityRoot\n}\n\ntype ResolverRoot interface {\n{{- range $object := .Objects -}}\n\t{{ if $object.HasResolvers -}}\n\t\t{{$object.Definition.GQLDefinition.Name}}() {{$object.Definition.GQLDefinition.Name}}Resolver\n\t{{ end }}\n{{- end }}\n}\n\ntype DirectiveRoot struct {\n{{ range $directive := .Directives }}\n\t{{ $directive.Declaration }}\n{{ end }}\n}\n\ntype ComplexityRoot struct {\n{{ range $object := .Objects }}\n\t{{ if not $object.IsReserved -}}\n\t\t{{ $object.Definition.GQLDefinition.Name|toCamel }} struct {\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{ if not $field.IsReserved -}}\n\t\t\t\t{{ $field.GQLName|toCamel }} {{ $field.ComplexitySignature }}\n\t\t\t{{ end }}\n\t\t{{- end }}\n\t\t}\n\t{{- end }}\n{{ end }}\n}\n\n{{ range $object := .Objects -}}\n\t{{ if $object.HasResolvers }}\n\t\ttype {{$object.Definition.GQLDefinition.Name}}Resolver interface {\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{ $field.ShortResolverDeclaration }}\n\t\t{{ end }}\n\t\t}\n\t{{- end }}\n{{- end }}\n\n{{ range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{ if $field.Args }}\n\t\t\tfunc (e *executableSchema){{ $field.ArgsFunc }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {\n\t\t\t{{ template \"args.gotpl\" $field.Args }}\n\t\t\t}\n\t\t{{ end }}\n\t{{ end }}\n{{- end }}\n\n{{ range $directive := .Directives }}\n\t{{ if $directive.Args }}\n\t\tfunc (e *executableSchema){{ $directive.ArgsFunc }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {\n\t\t{{ template \"args.gotpl\" $directive.Args }}\n\t\t}\n\t{{ end }}\n{{ end }}\n\ntype executableSchema struct {\n\tresolvers ResolverRoot\n\tdirectives DirectiveRoot\n\tcomplexity ComplexityRoot\n}\n\nfunc (e *executableSchema) Schema() *ast.Schema {\n\treturn parsedSchema\n}\n\nfunc (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) {\n\tswitch typeName + \".\" + field {\n\t{{ range $object := .Objects }}\n\t\t{{ if not $object.IsReserved }}\n\t\t\t{{ range $field := $object.Fields }}\n\t\t\t\t{{ if not $field.IsReserved }}\n\t\t\t\t\tcase \"{{$object.Definition.GQLDefinition.Name}}.{{$field.GQLName}}\":\n\t\t\t\t\t\tif e.complexity.{{$object.Definition.GQLDefinition.Name|toCamel}}.{{$field.GQLName|toCamel}} == nil {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{{ if $field.Args }}\n\t\t\t\t\t\t\targs, err := e.{{ $field.ArgsFunc }}(context.TODO(),rawArgs)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn 0, false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t{{ end }}\n\t\t\t\t\t\treturn e.complexity.{{$object.Definition.GQLDefinition.Name|toCamel}}.{{$field.GQLName|toCamel}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{end}}), true\n\t\t\t\t{{ end }}\n\t\t\t{{ end }}\n\t\t{{ end }}\n\t{{ end }}\n\t}\n\treturn 0, false\n}\n\nfunc (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.QueryRoot.Definition.GQLDefinition.Name}}(ctx, op.SelectionSet)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t\tExtensions: ec.Extensions,\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"queries are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.MutationRoot.Definition.GQLDefinition.Name}}(ctx, op.SelectionSet)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t\tExtensions: ec.Extensions,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"mutations are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e}\n\n\t\tnext := ec._{{.SubscriptionRoot.Definition.GQLDefinition.Name}}(ctx, op.SelectionSet)\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 := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\t\tbuf.Reset()\n\t\t\t\tdata := next()\n\n\t\t\t\tif data == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tdata.MarshalGQL(&buf)\n\t\t\t\treturn buf.Bytes()\n\t\t\t})\n\n\t\t\tif buf == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf,\n\t\t\t\tErrors: ec.Errors,\n\t\t\t\tExtensions: ec.Extensions,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(graphql.ErrorResponse(ctx, \"subscriptions are not supported\"))\n\t{{- end }}\n}\n\ntype executionContext struct {\n\t*graphql.RequestContext\n\t*executableSchema\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\nfunc (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tec.Error(ctx, ec.Recover(ctx, r))\n\t\t\tret = nil\n\t\t}\n\t}()\n\t{{- if .Directives }}\n\trctx := graphql.GetResolverContext(ctx)\n\tfor _, d := range rctx.Field.Definition.Directives {\n\t\tswitch d.Name {\n\t\t{{- range $directive := .Directives }}\n\t\tcase \"{{$directive.Name}}\":\n\t\t\tif ec.directives.{{$directive.Name|ucFirst}} != nil {\n\t\t\t\t{{- if $directive.Args }}\n\t\t\t\t\trawArgs := d.ArgumentMap(ec.Variables)\n\t\t\t\t\targs, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tec.Error(ctx, err)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t\tn := next\n\t\t\t\tnext = func(ctx context.Context) (interface{}, error) {\n\t\t\t\t\treturn ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})\n\t\t\t\t}\n\t\t\t}\n\t\t{{- end }}\n\t\t}\n\t}\n\t{{- end }}\n\tres, err := ec.ResolverMiddleware(ctx, next)\n\tif err != nil {\n\t\tec.Error(ctx, err)\n\t\treturn nil\n\t}\n\treturn res\n}\n\nfunc (ec *executionContext) introspectSchema() (*introspection.Schema, error) {\n\tif ec.DisableIntrospection {\n\t\treturn nil, errors.New(\"introspection disabled\")\n\t}\n\treturn introspection.WrapSchema(parsedSchema), nil\n}\n\nfunc (ec *executionContext) introspectType(name string) (*introspection.Type, error) {\n\tif ec.DisableIntrospection {\n\t\treturn nil, errors.New(\"introspection disabled\")\n\t}\n\treturn introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil\n}\n\nvar parsedSchema = gqlparser.MustLoadSchema(\n\t{{- range $filename, $schema := .SchemaRaw }}\n\t\t&ast.Source{Name: {{$filename|quote}}, Input: {{$schema|rawQuote}}},\n\t{{- end }}\n)\n\n\n\n// ChainFieldMiddleware add chain by FieldMiddleware\n// nolint: deadcode\nfunc chainFieldMiddleware(handleFunc ...graphql.FieldMiddleware) graphql.FieldMiddleware {\n\tn := len(handleFunc)\n\n\tif n > 1 {\n\t\tlastI := n - 1\n\t\treturn func(ctx context.Context, next graphql.Resolver) (interface{}, error) {\n\t\t\tvar (\n\t\t\t\tchainHandler graphql.Resolver\n\t\t\t\tcurI int\n\t\t\t)\n\t\t\tchainHandler = func(currentCtx context.Context) (interface{}, error) {\n\t\t\t\tif curI == lastI {\n\t\t\t\t\treturn next(currentCtx)\n\t\t\t\t}\n\t\t\t\tcurI++\n\t\t\t\tres, err := handleFunc[curI](currentCtx, chainHandler)\n\t\t\t\tcurI--\n\t\t\t\treturn res, err\n\n\t\t\t}\n\t\t\treturn handleFunc[0](ctx, chainHandler)\n\t\t}\n\t}\n\n\tif n == 1 {\n\t\treturn handleFunc[0]\n\t}\n\n\treturn func(ctx context.Context, next graphql.Resolver) (interface{}, error) {\n\t\treturn next(ctx)\n\t}\n}\n", + "generated.gotpl": "// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\npackage {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\n// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.\nfunc NewExecutableSchema(cfg Config) graphql.ExecutableSchema {\n\treturn &executableSchema{\n\t\tresolvers: cfg.Resolvers,\n\t\tdirectives: cfg.Directives,\n\t\tcomplexity: cfg.Complexity,\n\t}\n}\n\ntype Config struct {\n\tResolvers ResolverRoot\n\tDirectives DirectiveRoot\n\tComplexity ComplexityRoot\n}\n\ntype ResolverRoot interface {\n{{- range $object := .Objects -}}\n\t{{ if $object.HasResolvers -}}\n\t\t{{$object.Definition.GQLDefinition.Name}}() {{$object.Definition.GQLDefinition.Name}}Resolver\n\t{{ end }}\n{{- end }}\n}\n\ntype DirectiveRoot struct {\n{{ range $directive := .Directives }}\n\t{{ $directive.Declaration }}\n{{ end }}\n}\n\ntype ComplexityRoot struct {\n{{ range $object := .Objects }}\n\t{{ if not $object.IsReserved -}}\n\t\t{{ $object.Definition.GQLDefinition.Name|toCamel }} struct {\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{ if not $field.IsReserved -}}\n\t\t\t\t{{ $field.GQLName|toCamel }} {{ $field.ComplexitySignature }}\n\t\t\t{{ end }}\n\t\t{{- end }}\n\t\t}\n\t{{- end }}\n{{ end }}\n}\n\n{{ range $object := .Objects -}}\n\t{{ if $object.HasResolvers }}\n\t\ttype {{$object.Definition.GQLDefinition.Name}}Resolver interface {\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{ $field.ShortResolverDeclaration }}\n\t\t{{ end }}\n\t\t}\n\t{{- end }}\n{{- end }}\n\n{{ range $object := .Objects -}}\n\t{{ range $field := $object.Fields -}}\n\t\t{{ if $field.Args }}\n\t\t\tfunc (e *executableSchema){{ $field.ArgsFunc }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {\n\t\t\t{{ template \"args.gotpl\" $field.Args }}\n\t\t\t}\n\t\t{{ end }}\n\t{{ end }}\n{{- end }}\n\n{{ range $directive := .Directives }}\n\t{{ if $directive.Args }}\n\t\tfunc (e *executableSchema){{ $directive.ArgsFunc }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {\n\t\t{{ template \"args.gotpl\" $directive.Args }}\n\t\t}\n\t{{ end }}\n{{ end }}\n\ntype executableSchema struct {\n\tresolvers ResolverRoot\n\tdirectives DirectiveRoot\n\tcomplexity ComplexityRoot\n}\n\nfunc (e *executableSchema) Schema() *ast.Schema {\n\treturn parsedSchema\n}\n\nfunc (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) {\n\tswitch typeName + \".\" + field {\n\t{{ range $object := .Objects }}\n\t\t{{ if not $object.IsReserved }}\n\t\t\t{{ range $field := $object.Fields }}\n\t\t\t\t{{ if not $field.IsReserved }}\n\t\t\t\t\tcase \"{{$object.Definition.GQLDefinition.Name}}.{{$field.GQLName}}\":\n\t\t\t\t\t\tif e.complexity.{{$object.Definition.GQLDefinition.Name|toCamel}}.{{$field.GQLName|toCamel}} == nil {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t{{ if $field.Args }}\n\t\t\t\t\t\t\targs, err := e.{{ $field.ArgsFunc }}(context.TODO(),rawArgs)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn 0, false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t{{ end }}\n\t\t\t\t\t\treturn e.complexity.{{$object.Definition.GQLDefinition.Name|toCamel}}.{{$field.GQLName|toCamel}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{end}}), true\n\t\t\t\t{{ end }}\n\t\t\t{{ end }}\n\t\t{{ end }}\n\t{{ end }}\n\t}\n\treturn 0, false\n}\n\nfunc (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {\n\t{{- if .QueryRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.QueryRoot.Definition.GQLDefinition.Name}}(ctx, op.SelectionSet)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t\tExtensions: ec.Extensions,\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"queries are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {\n\t{{- if .MutationRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e}\n\n\t\tbuf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\tdata := ec._{{.MutationRoot.Definition.GQLDefinition.Name}}(ctx, op.SelectionSet)\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata.MarshalGQL(&buf)\n\t\t\treturn buf.Bytes()\n\t\t})\n\n\t\treturn &graphql.Response{\n\t\t\tData: buf,\n\t\t\tErrors: ec.Errors,\n\t\t\tExtensions: ec.Extensions,\n\t\t}\n\t{{- else }}\n\t\treturn graphql.ErrorResponse(ctx, \"mutations are not supported\")\n\t{{- end }}\n}\n\nfunc (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {\n\t{{- if .SubscriptionRoot }}\n\t\tec := executionContext{graphql.GetRequestContext(ctx), e}\n\n\t\tnext := ec._{{.SubscriptionRoot.Definition.GQLDefinition.Name}}(ctx, op.SelectionSet)\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 := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {\n\t\t\t\tbuf.Reset()\n\t\t\t\tdata := next()\n\n\t\t\t\tif data == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tdata.MarshalGQL(&buf)\n\t\t\t\treturn buf.Bytes()\n\t\t\t})\n\n\t\t\tif buf == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn &graphql.Response{\n\t\t\t\tData: buf,\n\t\t\t\tErrors: ec.Errors,\n\t\t\t\tExtensions: ec.Extensions,\n\t\t\t}\n\t\t}\n\t{{- else }}\n\t\treturn graphql.OneShot(graphql.ErrorResponse(ctx, \"subscriptions are not supported\"))\n\t{{- end }}\n}\n\ntype executionContext struct {\n\t*graphql.RequestContext\n\t*executableSchema\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\nfunc (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tec.Error(ctx, ec.Recover(ctx, r))\n\t\t\tret = nil\n\t\t}\n\t}()\n\t{{- if .Directives }}\n\trctx := graphql.GetResolverContext(ctx)\n\tfor _, d := range rctx.Field.Definition.Directives {\n\t\tswitch d.Name {\n\t\t{{- range $directive := .Directives }}\n\t\tcase \"{{$directive.Name}}\":\n\t\t\tif ec.directives.{{$directive.Name|ucFirst}} != nil {\n\t\t\t\t{{- if $directive.Args }}\n\t\t\t\t\trawArgs := d.ArgumentMap(ec.Variables)\n\t\t\t\t\targs, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tec.Error(ctx, err)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t\tn := next\n\t\t\t\tnext = func(ctx context.Context) (interface{}, error) {\n\t\t\t\t\treturn ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})\n\t\t\t\t}\n\t\t\t}\n\t\t{{- end }}\n\t\t}\n\t}\n\t{{- end }}\n\tres, err := ec.ResolverMiddleware(ctx, next)\n\tif err != nil {\n\t\tec.Error(ctx, err)\n\t\treturn nil\n\t}\n\treturn res\n}\n\nfunc (ec *executionContext) introspectSchema() (*introspection.Schema, error) {\n\tif ec.DisableIntrospection {\n\t\treturn nil, errors.New(\"introspection disabled\")\n\t}\n\treturn introspection.WrapSchema(parsedSchema), nil\n}\n\nfunc (ec *executionContext) introspectType(name string) (*introspection.Type, error) {\n\tif ec.DisableIntrospection {\n\t\treturn nil, errors.New(\"introspection disabled\")\n\t}\n\treturn introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil\n}\n\nvar parsedSchema = gqlparser.MustLoadSchema(\n\t{{- range $filename, $schema := .SchemaStr }}\n\t\t&ast.Source{Name: {{$filename|quote}}, Input: {{$schema|rawQuote}}},\n\t{{- end }}\n)\n\n\n\n// ChainFieldMiddleware add chain by FieldMiddleware\n// nolint: deadcode\nfunc chainFieldMiddleware(handleFunc ...graphql.FieldMiddleware) graphql.FieldMiddleware {\n\tn := len(handleFunc)\n\n\tif n > 1 {\n\t\tlastI := n - 1\n\t\treturn func(ctx context.Context, next graphql.Resolver) (interface{}, error) {\n\t\t\tvar (\n\t\t\t\tchainHandler graphql.Resolver\n\t\t\t\tcurI int\n\t\t\t)\n\t\t\tchainHandler = func(currentCtx context.Context) (interface{}, error) {\n\t\t\t\tif curI == lastI {\n\t\t\t\t\treturn next(currentCtx)\n\t\t\t\t}\n\t\t\t\tcurI++\n\t\t\t\tres, err := handleFunc[curI](currentCtx, chainHandler)\n\t\t\t\tcurI--\n\t\t\t\treturn res, err\n\n\t\t\t}\n\t\t\treturn handleFunc[0](ctx, chainHandler)\n\t\t}\n\t}\n\n\tif n == 1 {\n\t\treturn handleFunc[0]\n\t}\n\n\treturn func(ctx context.Context, next graphql.Resolver) (interface{}, error) {\n\t\treturn next(ctx)\n\t}\n}\n", "input.gotpl": "\t{{- if .Definition.IsMarshaled }}\n\tfunc Unmarshal{{ .Definition.GQLDefinition.Name }}(v interface{}) ({{.Definition.GoType | ref}}, error) {\n\t\tvar it {{.Definition.GoType | ref}}\n\t\tvar asMap = v.(map[string]interface{})\n\t\t{{ range $field := .Fields}}\n\t\t\t{{- if $field.Default}}\n\t\t\t\tif _, present := asMap[{{$field.GQLName|quote}}] ; !present {\n\t\t\t\t\tasMap[{{$field.GQLName|quote}}] = {{ $field.Default | dump }}\n\t\t\t\t}\n\t\t\t{{- end}}\n\t\t{{- end }}\n\n\t\tfor k, v := range asMap {\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.GoFieldName) \"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\n\tfunc (e *executableSchema) {{ .Definition.GQLDefinition.Name }}Middleware(ctx context.Context, obj *{{.Definition.GoType | ref}}) (*{{.Definition.GoType | ref}}, error) {\n\t\t\t{{ if .Directives }}\n\t\tcObj, err := chainFieldMiddleware(\n\t\t\t[]graphql.FieldMiddleware{\n\t\t\t\t{{- range $directive := .Directives }}\n\t\t\t\t\tfunc(ctx context.Context, n graphql.Resolver) (res interface{}, err error) {\n\t\t\t\t\t{{- if $directive.Args }}\n\t\t\t\t\t{{- range $arg := $directive.Args }}\n\t\t\t\t\t\t{{- if and $arg.IsPtr ( notNil \"Value\" $arg ) }}\n\t\t\t\t\t\t\t{{ $arg.GoVarName }} := {{ $arg.Value | dump }}\n\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t{{- end }}\n\t\t\t\t\t{{- end -}}\n\t\t\t\t\t\treturn e.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs \"obj\" \"n\"}})\n\t\t\t\t\t},\n\t\t\t\t{{ end }}\n\t\t\t}...\n\t\t)(ctx, func(ctx context.Context)(interface{}, error){\n\t\t\treturn obj, nil\n\t\t})\n\t\tif err != nil || cObj == nil {\n\t\t\treturn nil ,err\n\t\t}\n\t\tobj, ok := cObj.(*{{.Definition.GoType | ref}})\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"expect {{.Definition.GoType | ref}}\")\n\t\t}\n\t\t{{ end }}\n\n\t\t{{- range $field := .Fields }}\n\t\t{{ if $field.HasDirectives }}\n\t\t\tc{{$field.GoFieldName}}, err := chainFieldMiddleware(\n\t\t\t\t[]graphql.FieldMiddleware{\n\t\t\t\t\t{{- range $directive := $field.Directives }}\n\t\t\t\t\t\tfunc(ctx context.Context, n graphql.Resolver) (res interface{}, err error) {\n\t\t\t\t\t\t{{- if $directive.Args }}\n\t\t\t\t\t\t{{- range $arg := $directive.Args }}\n\t\t\t\t\t\t\t{{- if and $arg.IsPtr ( notNil \"Value\" $arg ) }}\n\t\t\t\t\t\t\t\t{{ $arg.GoVarName }} := {{ $arg.Value | dump }}\n\t\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t\t\t{{ if $field.IsPtr -}}\n\t\t\t\t\t\t\t\treturn e.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs ( print \"*obj.\" $field.GoFieldName ) \"n\"}})\n\t\t\t\t\t\t\t{{- else -}}\n\t\t\t\t\t\t\t\treturn e.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs ( print \"obj.\" $field.GoFieldName ) \"n\"}})\n\t\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t\t},\n\t\t\t\t\t{{ end }}\n\t\t\t\t}...\n\t\t\t)(ctx, func(ctx context.Context)(interface{}, error){\n\t\t\t\treturn {{ if $field.IsPtr }}*{{end}}obj.{{$field.GoFieldName}}, nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn obj ,err\n\t\t\t}\n\n\t\t\t{{ if $field.IsPtr }}\n\t\t\t\tif data, ok := c{{$field.GoFieldName}}.({{ $field.Definition.GoType | ref }}); ok {\n \t\tobj.{{$field.GoFieldName}} = &data\n \t} else {\n \t\treturn obj, errors.New(\"expect {{ $field.GoType | ref }}\")\n \t}\n\t\t\t{{else}}\n \tif data, ok := c{{$field.GoFieldName}}.({{ $field.GoType | ref }}); ok{\n \t\tobj.{{$field.GoFieldName}} = data\n \t}else{\n \t\treturn obj, errors.New(\"{{$field.GoFieldName}} expect {{$field.GoType | ref }}\")\n \t}\n\t\t\t{{ end }}\n\n\t\t\t{{- end }}\n\n\t\t\t{{ if eq $field.Definition.GQLDefinition.Kind \"INPUT_OBJECT\" }}\n\t\t\t\t{{ $field.Middleware (print \"obj.\" $field.GoFieldName ) (print \"obj.\" $field.GoFieldName ) }}\n\t\t\t{{- end }}\n\t\t{{- end }}\n\t\treturn obj, nil\n\t}\n", "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.Definition.GQLDefinition.Name}}(ctx context.Context, sel ast.SelectionSet, obj *{{$interface.Definition.GoType | ref}}) graphql.Marshaler {\n\tswitch obj := (*obj).(type) {\n\tcase nil:\n\t\treturn graphql.Null\n\t{{- range $implementor := $interface.Implementors }}\n\t\t{{- if $implementor.ValueReceiver }}\n\t\t\tcase {{$implementor.Definition.GoType | ref}}:\n\t\t\t\treturn ec._{{$implementor.Definition.GQLDefinition.Name}}(ctx, sel, &obj)\n\t\t{{- end}}\n\t\tcase *{{$implementor.Definition.GoType | ref}}:\n\t\t\treturn ec._{{$implementor.Definition.GQLDefinition.Name}}(ctx, sel, obj)\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", - "models.gotpl": "// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\npackage {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\n{{ range $model := .Models }}\n\t{{with .Description}} {{.|prefixLines \"// \"}} {{end}}\n\t{{- if .Definition.GQLDefinition.IsAbstractType }}\n\t\ttype {{.Definition.GoType | ref }} interface {\n\t\t\tIs{{.Definition.GoType | ref }}()\n\t\t}\n\t{{- else }}\n\t\ttype {{.Definition.GoType | ref }} struct {\n\t\t\t{{- range $field := .Fields }}\n\t\t\t\t{{- with .Description}}\n\t\t\t\t\t{{.|prefixLines \"// \"}}\n\t\t\t\t{{- end}}\n\t\t\t\t{{ $field.GoFieldName }} {{$field.GoType | ref}} `json:\"{{$field.GQLName}}\"`\n\t\t\t{{- end }}\n\t\t}\n\n\t\t{{- range $iface := .Implements }}\n\t\t\tfunc ({{$model.Definition.GoType | ref }}) Is{{$iface.GoType | ref }}() {}\n\t\t{{- end }}\n\n\t{{- end }}\n{{- end}}\n\n{{ range $enum := .Enums }}\n\t{{with .Description}}{{.|prefixLines \"// \"}} {{end}}\n\ttype {{.Definition.GoType | ref }} string\n\tconst (\n\t{{- range $value := .Values}}\n\t\t{{- with .Description}}\n\t\t\t{{.|prefixLines \"// \"}}\n\t\t{{- end}}\n\t\t{{$enum.Definition.GoType | ref }}{{ .Name|toCamel }} {{$enum.Definition.GoType | ref }} = {{.Name|quote}}\n\t{{- end }}\n\t)\n\n\tvar All{{.Definition.GoType | ref }} = []{{.Definition.GoType | ref }}{\n\t{{- range $value := .Values}}\n\t\t{{$enum.Definition.GoType | ref }}{{ .Name|toCamel }},\n\t{{- end }}\n\t}\n\n\tfunc (e {{.Definition.GoType | ref }}) IsValid() bool {\n\t\tswitch e {\n\t\tcase {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.Definition.GoType | ref }}{{ $element.Name|toCamel }}{{end}}:\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tfunc (e {{.Definition.GoType | ref }}) String() string {\n\t\treturn string(e)\n\t}\n\n\tfunc (e *{{.Definition.GoType | ref }}) UnmarshalGQL(v interface{}) error {\n\t\tstr, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"enums must be strings\")\n\t\t}\n\n\t\t*e = {{.Definition.GoType | ref }}(str)\n\t\tif !e.IsValid() {\n\t\t\treturn fmt.Errorf(\"%s is not a valid {{.Definition.GQLDefinition.Name}}\", str)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfunc (e {{.Definition.GoType | ref }}) MarshalGQL(w io.Writer) {\n\t\tfmt.Fprint(w, strconv.Quote(e.String()))\n\t}\n\n{{- end }}\n", + "models.gotpl": "// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\npackage {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\n{{- range $model := .Interfaces }}\n\t{{with .Definition.GQLDefinition.Description }} {{.|prefixLines \"// \"}} {{end}}\n\ttype {{.Definition.GoType | ref }} interface {\n\t\tIs{{.Definition.GoType | ref }}()\n\t}\n{{- end }}\n\n{{ range $model := .Models }}\n\t{{with .Definition.GQLDefinition.Description }} {{.|prefixLines \"// \"}} {{end}}\n\ttype {{.Definition.GoType | ref }} struct {\n\t\t{{- range $field := .Fields }}\n\t\t\t{{- with .Definition.GQLDefinition.Description }}\n\t\t\t\t{{.|prefixLines \"// \"}}\n\t\t\t{{- end}}\n\t\t\t{{ $field.GoFieldName }} {{$field.GoType | ref}} `json:\"{{$field.GQLName}}\"`\n\t\t{{- end }}\n\t}\n\n\t{{- range $iface := .Implements }}\n\t\tfunc ({{$model.Definition.GoType | ref }}) Is{{$iface.GoType | ref }}() {}\n\t{{- end }}\n{{- end}}\n\n{{ range $enum := .Enums }}\n\t{{with .Definition.GQLDefinition.Description }} {{.|prefixLines \"// \"}} {{end}}\n\ttype {{.Definition.GoType | ref }} string\n\tconst (\n\t{{- range $value := .Values}}\n\t\t{{- with .Description}}\n\t\t\t{{.|prefixLines \"// \"}}\n\t\t{{- end}}\n\t\t{{$enum.Definition.GoType | ref }}{{ .Name|toCamel }} {{$enum.Definition.GoType | ref }} = {{.Name|quote}}\n\t{{- end }}\n\t)\n\n\tvar All{{.Definition.GoType | ref }} = []{{.Definition.GoType | ref }}{\n\t{{- range $value := .Values}}\n\t\t{{$enum.Definition.GoType | ref }}{{ .Name|toCamel }},\n\t{{- end }}\n\t}\n\n\tfunc (e {{.Definition.GoType | ref }}) IsValid() bool {\n\t\tswitch e {\n\t\tcase {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.Definition.GoType | ref }}{{ $element.Name|toCamel }}{{end}}:\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tfunc (e {{.Definition.GoType | ref }}) String() string {\n\t\treturn string(e)\n\t}\n\n\tfunc (e *{{.Definition.GoType | ref }}) UnmarshalGQL(v interface{}) error {\n\t\tstr, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"enums must be strings\")\n\t\t}\n\n\t\t*e = {{.Definition.GoType | ref }}(str)\n\t\tif !e.IsValid() {\n\t\t\treturn fmt.Errorf(\"%s is not a valid {{.Definition.GQLDefinition.Name}}\", str)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfunc (e {{.Definition.GoType | ref }}) MarshalGQL(w io.Writer) {\n\t\tfmt.Fprint(w, strconv.Quote(e.String()))\n\t}\n\n{{- end }}\n", "object.gotpl": "{{ $object := . }}\n\nvar {{ $object.Definition.GQLDefinition.Name|lcFirst}}Implementors = {{$object.Implementors}}\n\n// nolint: gocyclo, errcheck, gas, goconst\n{{- if .Stream }}\nfunc (ec *executionContext) _{{$object.Definition.GQLDefinition.Name}}(ctx context.Context, sel ast.SelectionSet) func() graphql.Marshaler {\n\tfields := graphql.CollectFields(ctx, sel, {{$object.Definition.GQLDefinition.Name|lcFirst}}Implementors)\n\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\tObject: {{$object.Definition.GQLDefinition.Name|quote}},\n\t})\n\tif len(fields) != 1 {\n\t\tec.Errorf(ctx, \"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.Definition.GQLDefinition.Name}}_{{$field.GQLName}}(ctx, 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.Definition.GQLDefinition.Name}}(ctx context.Context, sel ast.SelectionSet{{if not $object.Root}}, obj *{{$object.Definition.GoType | ref }} {{end}}) graphql.Marshaler {\n\tfields := graphql.CollectFields(ctx, sel, {{$object.Definition.GQLDefinition.Name|lcFirst}}Implementors)\n\t{{if $object.Root}}\n\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\t\tObject: {{$object.Definition.GQLDefinition.Name|quote}},\n\t\t})\n\t{{end}}\n\n\tout := graphql.NewFieldSet(fields)\n\tinvalid := false\n\tfor i, field := range fields {\n\t\tswitch field.Name {\n\t\tcase \"__typename\":\n\t\t\tout.Values[i] = graphql.MarshalString({{$object.Definition.GQLDefinition.Name|quote}})\n\t\t{{- range $field := $object.Fields }}\n\t\tcase \"{{$field.GQLName}}\":\n\t\t\t{{- if $field.IsConcurrent }}\n\t\t\t\tfield := field\n\t\t\t\tout.Concurrently(i, func() (res graphql.Marshaler) {\n\t\t\t\t\tres = ec._{{$object.Definition.GQLDefinition.Name}}_{{$field.GQLName}}(ctx, field{{if not $object.Root}}, obj{{end}})\n\t\t\t\t\t{{- if $field.ASTType.NonNull }}\n\t\t\t\t\t\tif res == graphql.Null {\n\t\t\t\t\t\t\tinvalid = true\n\t\t\t\t\t\t}\n\t\t\t\t\t{{- end }}\n\t\t\t\t\treturn res\n\t\t\t\t})\n\t\t\t{{- else }}\n\t\t\t\tout.Values[i] = ec._{{$object.Definition.GQLDefinition.Name}}_{{$field.GQLName}}(ctx, field{{if not $object.Root}}, obj{{end}})\n\t\t\t\t{{- if $field.ASTType.NonNull }}\n\t\t\t\t\tif out.Values[i] == graphql.Null {\n\t\t\t\t\t\tinvalid = true\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t{{- end }}\n\t\t{{- end }}\n\t\tdefault:\n\t\t\tpanic(\"unknown field \" + strconv.Quote(field.Name))\n\t\t}\n\t}\n\tout.Dispatch()\n\tif invalid { return graphql.Null }\n\treturn out\n}\n{{- end }}\n", "resolver.gotpl": "package {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/99designs/gqlgen/handler\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\ntype {{.ResolverType}} struct {}\n\n{{ range $object := .Objects -}}\n\t{{- if $object.HasResolvers -}}\n\t\tfunc (r *{{$.ResolverType}}) {{$object.Definition.GQLDefinition.Name}}() {{ $object.ResolverInterface | ref }} {\n\t\t\treturn &{{lcFirst $object.Definition.GQLDefinition.Name}}Resolver{r}\n\t\t}\n\t{{ end -}}\n{{ end }}\n\n{{ range $object := .Objects -}}\n\t{{- if $object.HasResolvers -}}\n\t\ttype {{lcFirst $object.Definition.GQLDefinition.Name}}Resolver struct { *Resolver }\n\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{- if $field.IsResolver -}}\n\t\t\tfunc (r *{{lcFirst $object.Definition.GQLDefinition.Name}}Resolver) {{ $field.ShortResolverDeclaration }} {\n\t\t\t\tpanic(\"not implemented\")\n\t\t\t}\n\t\t\t{{ end -}}\n\t\t{{ end -}}\n\t{{ end -}}\n{{ end }}\n", "server.gotpl": "package main\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"log\" }}\n\t{{ reserveImport \"net/http\" }}\n\t{{ reserveImport \"os\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/handler\" }}\n)\n\nconst defaultPort = \"8080\"\n\nfunc main() {\n\tport := os.Getenv(\"PORT\")\n\tif port == \"\" {\n\t\tport = defaultPort\n\t}\n\n\thttp.Handle(\"/\", handler.Playground(\"GraphQL playground\", \"/query\"))\n\thttp.Handle(\"/query\", handler.GraphQL({{ lookupImport .ExecPackageName }}.NewExecutableSchema({{ lookupImport .ExecPackageName}}.Config{Resolvers: &{{ lookupImport .ResolverPackageName}}.Resolver{}})))\n\n\tlog.Printf(\"connect to http://localhost:%s/ for GraphQL playground\", port)\n\tlog.Fatal(http.ListenAndServe(\":\" + port, nil))\n}\n", diff --git a/codegen/templates/generated.gotpl b/codegen/templates/generated.gotpl index 5b346eb62b..d275aac920 100644 --- a/codegen/templates/generated.gotpl +++ b/codegen/templates/generated.gotpl @@ -279,7 +279,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var parsedSchema = gqlparser.MustLoadSchema( - {{- range $filename, $schema := .SchemaRaw }} + {{- range $filename, $schema := .SchemaStr }} &ast.Source{Name: {{$filename|quote}}, Input: {{$schema|rawQuote}}}, {{- end }} ) diff --git a/codegen/templates/models.gotpl b/codegen/templates/models.gotpl index 50c149fbf9..8e1071e8c5 100644 --- a/codegen/templates/models.gotpl +++ b/codegen/templates/models.gotpl @@ -20,31 +20,31 @@ import ( {{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} ) -{{ range $model := .Models }} - {{with .Description}} {{.|prefixLines "// "}} {{end}} - {{- if .Definition.GQLDefinition.IsAbstractType }} - type {{.Definition.GoType | ref }} interface { - Is{{.Definition.GoType | ref }}() - } - {{- else }} - type {{.Definition.GoType | ref }} struct { - {{- range $field := .Fields }} - {{- with .Description}} - {{.|prefixLines "// "}} - {{- end}} - {{ $field.GoFieldName }} {{$field.GoType | ref}} `json:"{{$field.GQLName}}"` - {{- end }} - } +{{- range $model := .Interfaces }} + {{with .Definition.GQLDefinition.Description }} {{.|prefixLines "// "}} {{end}} + type {{.Definition.GoType | ref }} interface { + Is{{.Definition.GoType | ref }}() + } +{{- end }} - {{- range $iface := .Implements }} - func ({{$model.Definition.GoType | ref }}) Is{{$iface.GoType | ref }}() {} +{{ range $model := .Models }} + {{with .Definition.GQLDefinition.Description }} {{.|prefixLines "// "}} {{end}} + type {{.Definition.GoType | ref }} struct { + {{- range $field := .Fields }} + {{- with .Definition.GQLDefinition.Description }} + {{.|prefixLines "// "}} + {{- end}} + {{ $field.GoFieldName }} {{$field.GoType | ref}} `json:"{{$field.GQLName}}"` {{- end }} + } + {{- range $iface := .Implements }} + func ({{$model.Definition.GoType | ref }}) Is{{$iface.GoType | ref }}() {} {{- end }} {{- end}} {{ range $enum := .Enums }} - {{with .Description}}{{.|prefixLines "// "}} {{end}} + {{with .Definition.GQLDefinition.Description }} {{.|prefixLines "// "}} {{end}} type {{.Definition.GoType | ref }} string const ( {{- range $value := .Values}} diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 44a3cc09d2..e45be8b7fb 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -203,7 +203,7 @@ func RenderToFile(tpl string, filename string, data interface{}) error { var buf *bytes.Buffer buf, err := Run(tpl, data) if err != nil { - return errors.Wrap(err, filename+" generation failed") + return errors.Wrap(err, filename) } b := bytes.Replace(buf.Bytes(), []byte("%%%IMPORTS%%%"), []byte(CurrentImports.String()), -1) diff --git a/codegen/testdata/generateserver.graphqls b/codegen/testdata/generateserver.graphqls new file mode 100644 index 0000000000..bcb0a83a37 --- /dev/null +++ b/codegen/testdata/generateserver.graphqls @@ -0,0 +1,11 @@ +type Query { + user: User +} +type User { + id: Int + fist_name: String +} +enum Status { + OK + ERROR +} diff --git a/codegen/type_build.go b/codegen/type_build.go deleted file mode 100644 index d253652c3c..0000000000 --- a/codegen/type_build.go +++ /dev/null @@ -1,96 +0,0 @@ -package codegen - -import ( - "fmt" - "go/types" - "strings" - - "github.com/99designs/gqlgen/codegen/templates" - "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/loader" -) - -// namedTypeFromSchema objects for every graphql type, including scalars. There should only be one instance of TypeReference for each thing -func (g *Generator) buildNamedTypes(prog *loader.Program) (NamedTypes, error) { - ts := map[string]*TypeDefinition{} - for _, schemaType := range g.schema.Types { - t := &TypeDefinition{ - GQLDefinition: schemaType, - } - ts[t.GQLDefinition.Name] = t - - var pkgName, typeName string - if userEntry, ok := g.Models[t.GQLDefinition.Name]; ok && userEntry.Model != "" { - // special case for maps - if userEntry.Model == "map[string]interface{}" { - t.GoType = types.NewMap(types.Typ[types.String], types.NewInterface(nil, nil).Complete()) - - continue - } - - pkgName, typeName = pkgAndType(userEntry.Model) - } else if t.GQLDefinition.Kind == ast.Scalar { - pkgName = "github.com/99designs/gqlgen/graphql" - typeName = "String" - } else { - // Missing models, but we need to set up the types so any references will point to the code that will - // get generated - t.GoType = types.NewNamed(types.NewTypeName(0, g.Config.Model.Pkg(), templates.ToCamel(t.GQLDefinition.Name), nil), nil, nil) - - continue - } - - if pkgName == "" { - return nil, fmt.Errorf("missing package name for %s", schemaType.Name) - } - - // External marshal functions - def, _ := findGoType(prog, pkgName, "Marshal"+typeName) - if f, isFunc := def.(*types.Func); isFunc { - sig := def.Type().(*types.Signature) - t.GoType = sig.Params().At(0).Type() - t.Marshaler = f - - unmarshal, err := findGoType(prog, pkgName, "Unmarshal"+typeName) - if err != nil { - return nil, errors.Wrapf(err, "unable to find unmarshal func for %s.%s", pkgName, typeName) - } - t.Unmarshaler = unmarshal.(*types.Func) - continue - } - - // Normal object binding - obj, err := findGoType(prog, pkgName, typeName) - if err != nil { - return nil, errors.Wrapf(err, "unable to find %s.%s", pkgName, typeName) - } - t.GoType = obj.Type() - - namedType := obj.Type().(*types.Named) - hasUnmarshal := false - for i := 0; i < namedType.NumMethods(); i++ { - switch namedType.Method(i).Name() { - case "UnmarshalGQL": - hasUnmarshal = true - } - } - - // Special case to reference generated unmarshal functions - if !hasUnmarshal { - t.Unmarshaler = types.NewFunc(0, g.Config.Exec.Pkg(), "Unmarshal"+schemaType.Name, nil) - } - - } - return ts, nil -} - -// take a string in the form github.com/package/blah.TypeReference and split it into package and type -func pkgAndType(name string) (string, string) { - parts := strings.Split(name, ".") - if len(parts) == 1 { - return "", name - } - - return normalizeVendor(strings.Join(parts[:len(parts)-1], ".")), parts[len(parts)-1] -} diff --git a/codegen/unified/build.go b/codegen/unified/build.go new file mode 100644 index 0000000000..6742ed61aa --- /dev/null +++ b/codegen/unified/build.go @@ -0,0 +1,146 @@ +package unified + +import ( + "go/types" + "sort" + + "fmt" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/pkg/errors" + "github.com/vektah/gqlparser/ast" +) + +func NewSchema(cfg *config.Config) (*Schema, error) { + g := Schema{ + Config: cfg, + } + + var err error + g.Schema, g.SchemaStr, err = cfg.LoadSchema() + if err != nil { + return nil, err + } + + err = cfg.Check() + if err != nil { + return nil, err + } + + progLoader := g.Config.NewLoaderWithoutErrors() + g.Program, err = progLoader.Load() + if err != nil { + return nil, errors.Wrap(err, "loading failed") + } + + g.NamedTypes = NamedTypes{} + + for _, schemaType := range g.Schema.Types { + g.NamedTypes[schemaType.Name], err = g.buildTypeDef(schemaType) + if err != nil { + return nil, errors.Wrap(err, "unable to build type definition") + } + } + + g.Directives, err = g.buildDirectives() + if err != nil { + return nil, err + } + + for _, schemaType := range g.Schema.Types { + switch schemaType.Kind { + case ast.Object: + obj, err := g.buildObject(schemaType) + if err != nil { + return nil, errors.Wrap(err, "unable to build object definition") + } + + g.Objects = append(g.Objects, obj) + case ast.InputObject: + input, err := g.buildInput(schemaType) + if err != nil { + return nil, errors.Wrap(err, "unable to build input definition") + } + + g.Inputs = append(g.Inputs, input) + + case ast.Union, ast.Interface: + g.Interfaces = append(g.Interfaces, g.buildInterface(schemaType)) + + case ast.Enum: + if enum := g.buildEnum(schemaType); enum != nil { + g.Enums = append(g.Enums, *enum) + } + } + } + + if err := g.injectIntrospectionRoots(); err != nil { + return nil, err + } + + sort.Slice(g.Objects, func(i, j int) bool { + return g.Objects[i].Definition.GQLDefinition.Name < g.Objects[j].Definition.GQLDefinition.Name + }) + + sort.Slice(g.Inputs, func(i, j int) bool { + return g.Inputs[i].Definition.GQLDefinition.Name < g.Inputs[j].Definition.GQLDefinition.Name + }) + + sort.Slice(g.Interfaces, func(i, j int) bool { + return g.Interfaces[i].Definition.GQLDefinition.Name < g.Interfaces[j].Definition.GQLDefinition.Name + }) + + sort.Slice(g.Enums, func(i, j int) bool { + return g.Enums[i].Definition.GQLDefinition.Name < g.Enums[j].Definition.GQLDefinition.Name + }) + + return &g, nil +} + +func (g *Schema) injectIntrospectionRoots() error { + obj := g.Objects.ByName(g.Schema.Query.Name) + if obj == nil { + return fmt.Errorf("root query type must be defined") + } + + typeType, err := g.FindGoType("github.com/99designs/gqlgen/graphql/introspection", "Type") + if err != nil { + return errors.Wrap(err, "unable to find root Type introspection type") + } + + obj.Fields = append(obj.Fields, &Field{ + TypeReference: &TypeReference{g.NamedTypes["__Type"], types.NewPointer(typeType.Type()), ast.NamedType("__Schema", nil)}, + GQLName: "__type", + GoFieldType: GoFieldMethod, + GoReceiverName: "ec", + GoFieldName: "introspectType", + Args: []FieldArgument{ + { + GQLName: "name", + TypeReference: &TypeReference{ + g.NamedTypes["String"], + types.Typ[types.String], + ast.NamedType("String", nil), + }, + Object: &Object{}, + }, + }, + Object: obj, + }) + + schemaType, err := g.FindGoType("github.com/99designs/gqlgen/graphql/introspection", "Schema") + if err != nil { + return errors.Wrap(err, "unable to find root Schema introspection type") + } + + obj.Fields = append(obj.Fields, &Field{ + TypeReference: &TypeReference{g.NamedTypes["__Schema"], types.NewPointer(schemaType.Type()), ast.NamedType("__Schema", nil)}, + GQLName: "__schema", + GoFieldType: GoFieldMethod, + GoReceiverName: "ec", + GoFieldName: "introspectSchema", + Object: obj, + }) + + return nil +} diff --git a/codegen/util.go b/codegen/unified/build_bind.go similarity index 52% rename from codegen/util.go rename to codegen/unified/build_bind.go index ff822ac9a6..fdc83b9a35 100644 --- a/codegen/util.go +++ b/codegen/unified/build_bind.go @@ -1,178 +1,14 @@ -package codegen +package unified import ( "fmt" "go/types" - "reflect" "regexp" "strings" "github.com/pkg/errors" - "golang.org/x/tools/go/loader" ) -func findGoType(prog *loader.Program, 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 := prog.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) -} - -func findGoNamedType(def types.Type) (*types.Named, error) { - if def == nil { - return nil, nil - } - - namedType, ok := def.(*types.Named) - if !ok { - return nil, errors.Errorf("expected %s to be a named type, instead found %T\n", def.String(), def) - } - - return namedType, nil -} - -func findGoInterface(def types.Type) (*types.Interface, error) { - if def == nil { - return nil, nil - } - namedType, err := findGoNamedType(def) - if err != nil { - return nil, err - } - if namedType == nil { - return nil, nil - } - - underlying, ok := namedType.Underlying().(*types.Interface) - if !ok { - return nil, errors.Errorf("expected %s to be a named interface, instead found %s", def.String(), namedType.String()) - } - - return underlying, nil -} - -func findMethod(typ *types.Named, name string) *types.Func { - for i := 0; i < typ.NumMethods(); i++ { - method := typ.Method(i) - if !method.Exported() { - continue - } - - if strings.EqualFold(method.Name(), name) { - return method - } - } - - if s, ok := typ.Underlying().(*types.Struct); ok { - for i := 0; i < s.NumFields(); i++ { - field := s.Field(i) - if !field.Anonymous() { - continue - } - - if named, ok := field.Type().(*types.Named); ok { - if f := findMethod(named, name); f != nil { - return f - } - } - } - } - - return nil -} - -func equalFieldName(source, target string) bool { - source = strings.Replace(source, "_", "", -1) - target = strings.Replace(target, "_", "", -1) - return strings.EqualFold(source, target) -} - -// findField attempts to match the name to a struct field with the following -// priorites: -// 1. If struct tag is passed then struct tag has highest priority -// 2. Field in an embedded struct -// 3. Actual Field name -func findField(typ *types.Struct, name, structTag string) (*types.Var, error) { - var foundField *types.Var - foundFieldWasTag := false - - for i := 0; i < typ.NumFields(); i++ { - field := typ.Field(i) - - if structTag != "" { - tags := reflect.StructTag(typ.Tag(i)) - if val, ok := tags.Lookup(structTag); ok { - if equalFieldName(val, name) { - if foundField != nil && foundFieldWasTag { - return nil, errors.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", structTag, val) - } - - foundField = field - foundFieldWasTag = true - } - } - } - - if field.Anonymous() { - - fieldType := field.Type() - - if ptr, ok := fieldType.(*types.Pointer); ok { - fieldType = ptr.Elem() - } - - // Type.Underlying() returns itself for all types except types.Named, where it returns a struct type. - // It should be safe to always call. - if named, ok := fieldType.Underlying().(*types.Struct); ok { - f, err := findField(named, name, structTag) - if err != nil && !strings.HasPrefix(err.Error(), "no field named") { - return nil, err - } - if f != nil && foundField == nil { - foundField = f - } - } - } - - if !field.Exported() { - continue - } - - if equalFieldName(field.Name(), name) && foundField == nil { // aqui! - foundField = field - } - } - - if foundField == nil { - return nil, fmt.Errorf("no field named %s", name) - } - - return foundField, nil -} - type BindError struct { object *Object field *Field @@ -183,7 +19,7 @@ type BindError struct { func (b BindError) Error() string { return fmt.Sprintf( - "Unable to bind %s.%s to %s\n %s\n %s", + "\nunable to bind %s.%s to %s\n %s\n %s", b.object.Definition.GQLDefinition.Name, b.field.GQLName, b.typ.String(), @@ -204,10 +40,8 @@ func (b BindErrors) Error() string { func bindObject(object *Object, structTag string) BindErrors { var errs BindErrors - for i := range object.Fields { - field := &object.Fields[i] - - if field.ForceResolver { + for _, field := range object.Fields { + if field.IsResolver { continue } @@ -221,6 +55,8 @@ func bindObject(object *Object, structTag string) BindErrors { varErr := bindVar(object.Definition.GoType, field, structTag) if varErr != nil { + field.IsResolver = true + errs = append(errs, BindError{ object: object, typ: object.Definition.GoType, @@ -318,7 +154,7 @@ nextArg: param := params.At(j) for _, oldArg := range field.Args { if strings.EqualFold(oldArg.GQLName, param.Name()) { - if !field.ForceResolver { + if !field.IsResolver { oldArg.TypeReference.GoType = param.Type() } newArgs = append(newArgs, oldArg) diff --git a/codegen/util_test.go b/codegen/unified/build_bind_test.go similarity index 99% rename from codegen/util_test.go rename to codegen/unified/build_bind_test.go index 37b54c4c4f..5e0f6f997c 100644 --- a/codegen/util_test.go +++ b/codegen/unified/build_bind_test.go @@ -1,4 +1,4 @@ -package codegen +package unified import ( "go/ast" diff --git a/codegen/directive_build.go b/codegen/unified/build_directive.go similarity index 62% rename from codegen/directive_build.go rename to codegen/unified/build_directive.go index e27a08ce47..0b7c7f53b5 100644 --- a/codegen/directive_build.go +++ b/codegen/unified/build_directive.go @@ -1,14 +1,16 @@ -package codegen +package unified import ( + "fmt" + "github.com/pkg/errors" "github.com/vektah/gqlparser/ast" ) -func (g *Generator) buildDirectives(types NamedTypes) (map[string]*Directive, error) { - directives := make(map[string]*Directive, len(g.schema.Directives)) +func (g *Schema) buildDirectives() (map[string]*Directive, error) { + directives := make(map[string]*Directive, len(g.Schema.Directives)) - for name, dir := range g.schema.Directives { + for name, dir := range g.Schema.Directives { if _, ok := directives[name]; ok { return nil, errors.Errorf("directive with name %s already exists", name) } @@ -21,7 +23,7 @@ func (g *Generator) buildDirectives(types NamedTypes) (map[string]*Directive, er newArg := FieldArgument{ GQLName: arg.Name, - TypeReference: types.getType(arg.Type), + TypeReference: g.NamedTypes.getType(arg.Type), GoVarName: sanitizeArgName(arg.Name), } @@ -48,8 +50,7 @@ func (g *Generator) buildDirectives(types NamedTypes) (map[string]*Directive, er return directives, nil } -func (g *Generator) getDirectives(list ast.DirectiveList) ([]*Directive, error) { - +func (g *Schema) getDirectives(list ast.DirectiveList) ([]*Directive, error) { dirs := make([]*Directive, len(list)) for i, d := range list { argValues := make(map[string]interface{}, len(d.Arguments)) @@ -60,27 +61,29 @@ func (g *Generator) getDirectives(list ast.DirectiveList) ([]*Directive, error) } argValues[da.Name] = val } + def, ok := g.Directives[d.Name] + if !ok { + return nil, fmt.Errorf("directive %s not found", d.Name) + } - if def, ok := g.Directives[d.Name]; ok { - var args []FieldArgument - for _, a := range def.Args { - - value := a.Default - if argValue, ok := argValues[a.GQLName]; ok { - value = argValue - } - args = append(args, FieldArgument{ - GQLName: a.GQLName, - Value: value, - GoVarName: a.GoVarName, - TypeReference: a.TypeReference, - }) - } - dirs[i] = &Directive{ - Name: d.Name, - Args: args, + var args []FieldArgument + for _, a := range def.Args { + value := a.Default + if argValue, ok := argValues[a.GQLName]; ok { + value = argValue } + args = append(args, FieldArgument{ + GQLName: a.GQLName, + Value: value, + GoVarName: a.GoVarName, + TypeReference: a.TypeReference, + }) } + dirs[i] = &Directive{ + Name: d.Name, + Args: args, + } + } return dirs, nil diff --git a/codegen/unified/build_enum.go b/codegen/unified/build_enum.go new file mode 100644 index 0000000000..5602e13802 --- /dev/null +++ b/codegen/unified/build_enum.go @@ -0,0 +1,31 @@ +package unified + +import ( + "go/types" + "strings" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/vektah/gqlparser/ast" +) + +func (g *Schema) buildEnum(typ *ast.Definition) *Enum { + namedType := g.NamedTypes[typ.Name] + if typ.Kind != ast.Enum || strings.HasPrefix(typ.Name, "__") || g.Config.Models.UserDefined(typ.Name) { + return nil + } + + var values []EnumValue + for _, v := range typ.EnumValues { + values = append(values, EnumValue{v.Name, v.Description}) + } + + enum := Enum{ + Definition: namedType, + Values: values, + InTypemap: g.Config.Models.UserDefined(typ.Name), + } + + enum.Definition.GoType = types.NewNamed(types.NewTypeName(0, g.Config.Model.Pkg(), templates.ToCamel(enum.Definition.GQLDefinition.Name), nil), nil, nil) + + return &enum +} diff --git a/codegen/unified/build_input.go b/codegen/unified/build_input.go new file mode 100644 index 0000000000..6f6b78c47d --- /dev/null +++ b/codegen/unified/build_input.go @@ -0,0 +1,47 @@ +package unified + +import ( + "go/types" + + "github.com/pkg/errors" + "github.com/vektah/gqlparser/ast" +) + +func (g *Schema) buildInput(typ *ast.Definition) (*Object, error) { + obj := &Object{ + Definition: g.NamedTypes[typ.Name], + InTypemap: g.Config.Models.UserDefined(typ.Name), + } + + for _, field := range typ.Fields { + newField, err := g.buildField(obj, field) + if err != nil { + return nil, err + } + + if !newField.TypeReference.Definition.GQLDefinition.IsInputType() { + return nil, errors.Errorf( + "%s cannot be used as a field of %s. only input and scalar types are allowed", + newField.Definition.GQLDefinition.Name, + obj.Definition.GQLDefinition.Name, + ) + } + + obj.Fields = append(obj.Fields, newField) + + } + dirs, err := g.getDirectives(typ.Directives) + if err != nil { + return nil, err + } + obj.Directives = dirs + + if _, isMap := obj.Definition.GoType.(*types.Map); !isMap && obj.InTypemap { + bindErrs := bindObject(obj, g.Config.StructTag) + if len(bindErrs) > 0 { + return nil, bindErrs + } + } + + return obj, nil +} diff --git a/codegen/unified/build_interface.go b/codegen/unified/build_interface.go new file mode 100644 index 0000000000..861d3415c8 --- /dev/null +++ b/codegen/unified/build_interface.go @@ -0,0 +1,50 @@ +package unified + +import ( + "go/types" + "strings" + + "github.com/vektah/gqlparser/ast" +) + +func (g *Schema) buildInterface(typ *ast.Definition) *Interface { + i := &Interface{ + Definition: g.NamedTypes[typ.Name], + InTypemap: g.Config.Models.UserDefined(typ.Name), + } + + for _, implementor := range g.Schema.GetPossibleTypes(typ) { + t := g.NamedTypes[implementor.Name] + + i.Implementors = append(i.Implementors, InterfaceImplementor{ + Definition: t, + ValueReceiver: g.isValueReceiver(g.NamedTypes[typ.Name], t), + }) + } + + return i +} + +func (g *Schema) isValueReceiver(intf *TypeDefinition, implementor *TypeDefinition) bool { + interfaceType, err := findGoInterface(intf.GoType) + if interfaceType == nil || err != nil { + return true + } + + implementorType, err := findGoNamedType(implementor.GoType) + if implementorType == nil || err != nil { + return true + } + + return types.Implements(implementorType, interfaceType) +} + +// take a string in the form github.com/package/blah.TypeReference and split it into package and type +func pkgAndType(name string) (string, string) { + parts := strings.Split(name, ".") + if len(parts) == 1 { + return "", name + } + + return normalizeVendor(strings.Join(parts[:len(parts)-1], ".")), parts[len(parts)-1] +} diff --git a/codegen/unified/build_object.go b/codegen/unified/build_object.go new file mode 100644 index 0000000000..f2f999ddaa --- /dev/null +++ b/codegen/unified/build_object.go @@ -0,0 +1,137 @@ +package unified + +import ( + "go/types" + "log" + + "strings" + + "github.com/pkg/errors" + "github.com/vektah/gqlparser/ast" +) + +func (g *Schema) buildObject(typ *ast.Definition) (*Object, error) { + obj := &Object{ + Definition: g.NamedTypes[typ.Name], + InTypemap: g.Config.Models.UserDefined(typ.Name), + } + + tt := types.NewTypeName(0, g.Config.Exec.Pkg(), obj.Definition.GQLDefinition.Name+"Resolver", nil) + obj.ResolverInterface = types.NewNamed(tt, nil, nil) + + if typ == g.Schema.Query { + obj.Root = true + obj.InTypemap = true + } + + if typ == g.Schema.Mutation { + obj.Root = true + obj.DisableConcurrency = true + obj.InTypemap = true + } + + if typ == g.Schema.Subscription { + obj.Root = true + obj.Stream = true + obj.InTypemap = true + } + + obj.Satisfies = append(obj.Satisfies, typ.Interfaces...) + + for _, intf := range g.Schema.GetImplements(typ) { + obj.Implements = append(obj.Implements, g.NamedTypes[intf.Name]) + } + + for _, field := range typ.Fields { + if strings.HasPrefix(field.Name, "__") { + continue + } + + f, err := g.buildField(obj, field) + if err != nil { + return nil, err + } + + obj.Fields = append(obj.Fields, f) + } + + dirs, err := g.getDirectives(typ.Directives) + if err != nil { + return nil, err + } + obj.Directives = dirs + + if _, isMap := obj.Definition.GoType.(*types.Map); !isMap && obj.InTypemap { + for _, bindErr := range bindObject(obj, g.Config.StructTag) { + log.Println(bindErr.Error()) + log.Println(" Adding resolver method") + } + } + + return obj, nil +} + +func (g *Schema) buildField(obj *Object, field *ast.FieldDefinition) (*Field, error) { + dirs, err := g.getDirectives(field.Directives) + if err != nil { + return nil, err + } + + f := Field{ + GQLName: field.Name, + TypeReference: g.NamedTypes.getType(field.Type), + Object: obj, + Directives: dirs, + GoFieldName: lintName(ucFirst(field.Name)), + GoFieldType: GoFieldVariable, + GoReceiverName: "obj", + } + + if field.DefaultValue != nil { + var err error + f.Default, err = field.DefaultValue.Value(nil) + if err != nil { + return nil, errors.Errorf("default value for %s.%s is not valid: %s", obj.Definition.GQLDefinition.Name, field.Name, err.Error()) + } + } + + typeEntry, entryExists := g.Config.Models[obj.Definition.GQLDefinition.Name] + if entryExists { + if typeField, ok := typeEntry.Fields[field.Name]; ok { + if typeField.Resolver { + f.IsResolver = true + } + if typeField.FieldName != "" { + f.GoFieldName = lintName(ucFirst(typeField.FieldName)) + } + } + } + + for _, arg := range field.Arguments { + argDirs, err := g.getDirectives(arg.Directives) + if err != nil { + return nil, err + } + newArg := FieldArgument{ + GQLName: arg.Name, + TypeReference: g.NamedTypes.getType(arg.Type), + Object: obj, + GoVarName: sanitizeArgName(arg.Name), + Directives: argDirs, + } + + if !newArg.TypeReference.Definition.GQLDefinition.IsInputType() { + return nil, errors.Errorf("%s cannot be used as argument of %s.%s. only input and scalar types are allowed", arg.Type, obj.Definition.GQLDefinition.Name, field.Name) + } + + if arg.DefaultValue != nil { + var err error + newArg.Default, err = arg.DefaultValue.Value(nil) + if err != nil { + return nil, errors.Errorf("default value for %s.%s is not valid: %s", obj.Definition.GQLDefinition.Name, field.Name, err.Error()) + } + } + f.Args = append(f.Args, newArg) + } + return &f, nil +} diff --git a/codegen/unified/build_typedef.go b/codegen/unified/build_typedef.go new file mode 100644 index 0000000000..0ef8fea288 --- /dev/null +++ b/codegen/unified/build_typedef.go @@ -0,0 +1,109 @@ +package unified + +import ( + "fmt" + "go/types" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/pkg/errors" + "github.com/vektah/gqlparser/ast" +) + +func (g *Schema) buildTypeDef(schemaType *ast.Definition) (*TypeDefinition, error) { + t := &TypeDefinition{ + GQLDefinition: schemaType, + } + + var pkgName, typeName string + if userEntry, ok := g.Config.Models[t.GQLDefinition.Name]; ok && userEntry.Model != "" { + // special case for maps + if userEntry.Model == "map[string]interface{}" { + t.GoType = types.NewMap(types.Typ[types.String], types.NewInterface(nil, nil).Complete()) + + return t, nil + } + + pkgName, typeName = pkgAndType(userEntry.Model) + } else if t.GQLDefinition.Kind == ast.Scalar { + pkgName = "github.com/99designs/gqlgen/graphql" + typeName = "String" + } else { + // Missing models, but we need to set up the types so any references will point to the code that will + // get generated + t.GoType = types.NewNamed(types.NewTypeName(0, g.Config.Model.Pkg(), templates.ToCamel(t.GQLDefinition.Name), nil), nil, nil) + + if t.GQLDefinition.Kind != ast.Enum { + t.Unmarshaler = types.NewFunc(0, g.Config.Exec.Pkg(), "Unmarshal"+schemaType.Name, nil) + } + + return t, nil + } + + if pkgName == "" { + return nil, fmt.Errorf("missing package name for %s", schemaType.Name) + } + + // External marshal functions + def, _ := g.FindGoType(pkgName, "Marshal"+typeName) + if f, isFunc := def.(*types.Func); isFunc { + sig := def.Type().(*types.Signature) + t.GoType = sig.Params().At(0).Type() + t.Marshaler = f + + unmarshal, err := g.FindGoType(pkgName, "Unmarshal"+typeName) + if err != nil { + return nil, errors.Wrapf(err, "unable to find unmarshal func for %s.%s", pkgName, typeName) + } + t.Unmarshaler = unmarshal.(*types.Func) + return t, nil + } + + // Normal object binding + obj, err := g.FindGoType(pkgName, typeName) + if err != nil { + return nil, errors.Wrapf(err, "unable to find %s.%s", pkgName, typeName) + } + t.GoType = obj.Type() + + namedType := obj.Type().(*types.Named) + hasUnmarshal := false + for i := 0; i < namedType.NumMethods(); i++ { + switch namedType.Method(i).Name() { + case "UnmarshalGQL": + hasUnmarshal = true + } + } + + // Special case to reference generated unmarshal functions + if !hasUnmarshal { + t.Unmarshaler = types.NewFunc(0, g.Config.Exec.Pkg(), "Unmarshal"+schemaType.Name, nil) + } + + return t, nil +} + +func (n NamedTypes) goTypeForAst(t *ast.Type) types.Type { + if t.Elem != nil { + return types.NewSlice(n.goTypeForAst(t.Elem)) + } + + nt := n[t.NamedType] + gt := nt.GoType + if gt == nil { + panic("missing type " + t.NamedType) + } + + if !t.NonNull && nt.GQLDefinition.Kind != ast.Interface { + return types.NewPointer(gt) + } + + return gt +} + +func (n NamedTypes) getType(t *ast.Type) *TypeReference { + return &TypeReference{ + Definition: n[t.Name()], + GoType: n.goTypeForAst(t), + ASTType: t, + } +} diff --git a/codegen/directive.go b/codegen/unified/directive.go similarity index 98% rename from codegen/directive.go rename to codegen/unified/directive.go index f9f000bafb..2cc65c244c 100644 --- a/codegen/directive.go +++ b/codegen/unified/directive.go @@ -1,4 +1,4 @@ -package codegen +package unified import ( "fmt" diff --git a/codegen/unified/enum.go b/codegen/unified/enum.go new file mode 100644 index 0000000000..e7f365f678 --- /dev/null +++ b/codegen/unified/enum.go @@ -0,0 +1,12 @@ +package unified + +type Enum struct { + Definition *TypeDefinition + Values []EnumValue + InTypemap bool +} + +type EnumValue struct { + Name string + Description string +} diff --git a/codegen/interface.go b/codegen/unified/interface.go similarity index 83% rename from codegen/interface.go rename to codegen/unified/interface.go index 045bdba88e..3517b853cf 100644 --- a/codegen/interface.go +++ b/codegen/unified/interface.go @@ -1,8 +1,9 @@ -package codegen +package unified type Interface struct { Definition *TypeDefinition Implementors []InterfaceImplementor + InTypemap bool } type InterfaceImplementor struct { diff --git a/codegen/object.go b/codegen/unified/object.go similarity index 89% rename from codegen/object.go rename to codegen/unified/object.go index afdbcf12e4..dd6dfec506 100644 --- a/codegen/object.go +++ b/codegen/unified/object.go @@ -1,4 +1,4 @@ -package codegen +package unified import ( "bytes" @@ -24,7 +24,7 @@ const ( type Object struct { Definition *TypeDefinition - Fields []Field + Fields []*Field Satisfies []string Implements []*TypeDefinition ResolverInterface types.Type @@ -32,17 +32,17 @@ type Object struct { DisableConcurrency bool Stream bool Directives []*Directive + InTypemap bool } type Field struct { *TypeReference - Description string // Description of a field GQLName string // The name of the field in graphql GoFieldType GoFieldType // The field type in go, if any GoReceiverName string // The name of method & var receiver in go, if any GoFieldName string // The name of the method or var in go, if any + IsResolver bool // Does this field need a resolver Args []FieldArgument // A list of arguments to be passed to this field - ForceResolver bool // Should be emit Resolver method MethodHasContext bool // If this is bound to a go method, does the method also take a context NoErr bool // If this is bound to a go method, does that method have an error as the second argument Object *Object // A link back to the parent object @@ -58,7 +58,7 @@ type FieldArgument struct { Object *Object // A link back to the parent object Default interface{} // The default value Directives []*Directive - Value interface{} // value set in schema + Value interface{} // value set in Schema } type Objects []*Object @@ -73,7 +73,7 @@ func (o *Object) Implementors() string { func (o *Object) HasResolvers() bool { for _, f := range o.Fields { - if f.IsResolver() { + if f.IsResolver { return true } } @@ -105,12 +105,12 @@ func (o *Object) IsReserved() bool { return strings.HasPrefix(o.Definition.GQLDefinition.Name, "__") } -func (f *Field) HasDirectives() bool { - return len(f.Directives) > 0 +func (o *Object) Description() string { + return o.Definition.GQLDefinition.Description } -func (f *Field) IsResolver() bool { - return f.GoFieldName == "" +func (f *Field) HasDirectives() bool { + return len(f.Directives) > 0 } func (f *Field) IsReserved() bool { @@ -129,11 +129,7 @@ func (f *Field) IsConcurrent() bool { if f.Object.DisableConcurrency { return false } - return f.MethodHasContext || f.IsResolver() -} - -func (f *Field) GoNameExported() string { - return lintName(ucFirst(f.GQLName)) + return f.MethodHasContext || f.IsResolver } func (f *Field) GoNameUnexported() string { @@ -141,11 +137,7 @@ func (f *Field) GoNameUnexported() string { } func (f *Field) ShortInvocation() string { - if !f.IsResolver() { - return "" - } - - return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.GQLDefinition.Name, f.GoNameExported(), f.CallArgs()) + return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.GQLDefinition.Name, f.GoFieldName, f.CallArgs()) } func (f *Field) ArgsFunc() string { @@ -157,40 +149,18 @@ func (f *Field) ArgsFunc() string { } func (f *Field) ResolverType() string { - if !f.IsResolver() { + if !f.IsResolver { return "" } - return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.GQLDefinition.Name, f.GoNameExported(), f.CallArgs()) + return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.GQLDefinition.Name, f.GoFieldName, f.CallArgs()) } func (f *Field) ShortResolverDeclaration() string { - if !f.IsResolver() { - return "" - } - res := fmt.Sprintf("%s(ctx context.Context", f.GoNameExported()) - - if !f.Object.Root { - res += fmt.Sprintf(", obj *%s", templates.CurrentImports.LookupType(f.Object.Definition.GoType)) - } - for _, arg := range f.Args { - res += fmt.Sprintf(", %s %s", arg.GoVarName, templates.CurrentImports.LookupType(arg.GoType)) - } - - result := templates.CurrentImports.LookupType(f.GoType) - if f.Object.Stream { - result = "<-chan " + result - } - - res += fmt.Sprintf(") (%s, error)", result) - return res -} - -func (f *Field) ResolverDeclaration() string { - if !f.IsResolver() { + if !f.IsResolver { return "" } - res := fmt.Sprintf("%s_%s(ctx context.Context", f.Object.Definition.GQLDefinition.Name, f.GoNameUnexported()) + res := fmt.Sprintf("%s(ctx context.Context", f.GoFieldName) if !f.Object.Root { res += fmt.Sprintf(", obj *%s", templates.CurrentImports.LookupType(f.Object.Definition.GoType)) @@ -229,7 +199,7 @@ func (f *Field) ComplexityArgs() string { func (f *Field) CallArgs() string { var args []string - if f.IsResolver() { + if f.IsResolver { args = append(args, "rctx") if !f.Object.Root { diff --git a/codegen/unified/schema.go b/codegen/unified/schema.go new file mode 100644 index 0000000000..315c3172b3 --- /dev/null +++ b/codegen/unified/schema.go @@ -0,0 +1,22 @@ +package unified + +import ( + "github.com/99designs/gqlgen/codegen/config" + "github.com/vektah/gqlparser/ast" + "golang.org/x/tools/go/loader" +) + +// Schema is the result of merging the GraphQL Schema with the existing go code +type Schema struct { + SchemaFilename config.SchemaFilenames + Config *config.Config + Schema *ast.Schema + SchemaStr map[string]string + Program *loader.Program + Directives map[string]*Directive + NamedTypes NamedTypes + Objects Objects + Inputs Objects + Interfaces []*Interface + Enums []Enum +} diff --git a/codegen/unified/schema_test.go b/codegen/unified/schema_test.go new file mode 100644 index 0000000000..599c485fa3 --- /dev/null +++ b/codegen/unified/schema_test.go @@ -0,0 +1,30 @@ +package unified + +import ( + "testing" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/stretchr/testify/require" +) + +func TestTypeUnionAsInput(t *testing.T) { + err := generate("inputunion", `testdata/unioninput.graphqls`) + + require.EqualError(t, err, "unable to build object definition: Bookmarkable! cannot be used as argument of Query.addBookmark. only input and scalar types are allowed") +} + +func TestTypeInInput(t *testing.T) { + err := generate("typeinput", `testdata/typeinput.graphqls`) + + require.EqualError(t, err, "unable to build input definition: Item cannot be used as a field of BookmarkableInput. only input and scalar types are allowed") +} + +func generate(name string, schemaFilename string) error { + _, err := NewSchema(&config.Config{ + SchemaFilename: config.SchemaFilenames{schemaFilename}, + Exec: config.PackageConfig{Filename: "gen/" + name + "/exec.go"}, + Model: config.PackageConfig{Filename: "gen/" + name + "/model.go"}, + }) + + return err +} diff --git a/codegen/unified/testdata/typeinput.graphqls b/codegen/unified/testdata/typeinput.graphqls new file mode 100644 index 0000000000..ba0438afca --- /dev/null +++ b/codegen/unified/testdata/typeinput.graphqls @@ -0,0 +1,7 @@ +type Query { + addBookmark(b: BookmarkableInput!): Boolean! +} +type Item {name: String} +input BookmarkableInput { + item: Item +} diff --git a/codegen/unified/testdata/unioninput.graphqls b/codegen/unified/testdata/unioninput.graphqls new file mode 100644 index 0000000000..b1a17b1dd1 --- /dev/null +++ b/codegen/unified/testdata/unioninput.graphqls @@ -0,0 +1,5 @@ +type Query { + addBookmark(b: Bookmarkable!): Boolean! +} +type Item {name: String} +union Bookmarkable = Item diff --git a/codegen/type_definition.go b/codegen/unified/type_definition.go similarity index 63% rename from codegen/type_definition.go rename to codegen/unified/type_definition.go index dd78aee136..49e91edf71 100644 --- a/codegen/type_definition.go +++ b/codegen/unified/type_definition.go @@ -1,4 +1,4 @@ -package codegen +package unified import ( "go/types" @@ -25,29 +25,3 @@ func (t TypeDefinition) IsEmptyInterface() bool { i, isInterface := t.GoType.(*types.Interface) return isInterface && i.NumMethods() == 0 } - -func (n NamedTypes) goTypeForAst(t *ast.Type) types.Type { - if t.Elem != nil { - return types.NewSlice(n.goTypeForAst(t.Elem)) - } - - nt := n[t.NamedType] - gt := nt.GoType - if gt == nil { - panic("missing type " + t.NamedType) - } - - if !t.NonNull && nt.GQLDefinition.Kind != ast.Interface { - return types.NewPointer(gt) - } - - return gt -} - -func (n NamedTypes) getType(t *ast.Type) *TypeReference { - return &TypeReference{ - Definition: n[t.Name()], - GoType: n.goTypeForAst(t), - ASTType: t, - } -} diff --git a/codegen/type_reference.go b/codegen/unified/type_reference.go similarity index 99% rename from codegen/type_reference.go rename to codegen/unified/type_reference.go index 107c62647f..b420eaf523 100644 --- a/codegen/type_reference.go +++ b/codegen/unified/type_reference.go @@ -1,4 +1,4 @@ -package codegen +package unified import ( "go/types" diff --git a/codegen/unified/util.go b/codegen/unified/util.go new file mode 100644 index 0000000000..fe61aeb20c --- /dev/null +++ b/codegen/unified/util.go @@ -0,0 +1,223 @@ +package unified + +import ( + "fmt" + "go/build" + "go/types" + "os" + "reflect" + "strings" + + "github.com/pkg/errors" +) + +func (g *Schema) 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 := g.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) +} + +func findGoNamedType(def types.Type) (*types.Named, error) { + if def == nil { + return nil, nil + } + + namedType, ok := def.(*types.Named) + if !ok { + return nil, errors.Errorf("expected %s to be a named type, instead found %T\n", def.String(), def) + } + + return namedType, nil +} + +func findGoInterface(def types.Type) (*types.Interface, error) { + if def == nil { + return nil, nil + } + namedType, err := findGoNamedType(def) + if err != nil { + return nil, err + } + if namedType == nil { + return nil, nil + } + + underlying, ok := namedType.Underlying().(*types.Interface) + if !ok { + return nil, errors.Errorf("expected %s to be a named interface, instead found %s", def.String(), namedType.String()) + } + + return underlying, nil +} + +func findMethod(typ *types.Named, name string) *types.Func { + for i := 0; i < typ.NumMethods(); i++ { + method := typ.Method(i) + if !method.Exported() { + continue + } + + if strings.EqualFold(method.Name(), name) { + return method + } + } + + if s, ok := typ.Underlying().(*types.Struct); ok { + for i := 0; i < s.NumFields(); i++ { + field := s.Field(i) + if !field.Anonymous() { + continue + } + + if named, ok := field.Type().(*types.Named); ok { + if f := findMethod(named, name); f != nil { + return f + } + } + } + } + + return nil +} + +func equalFieldName(source, target string) bool { + source = strings.Replace(source, "_", "", -1) + target = strings.Replace(target, "_", "", -1) + return strings.EqualFold(source, target) +} + +// findField attempts to match the name to a struct field with the following +// priorites: +// 1. If struct tag is passed then struct tag has highest priority +// 2. Field in an embedded struct +// 3. Actual Field name +func findField(typ *types.Struct, name, structTag string) (*types.Var, error) { + var foundField *types.Var + foundFieldWasTag := false + + for i := 0; i < typ.NumFields(); i++ { + field := typ.Field(i) + + if structTag != "" { + tags := reflect.StructTag(typ.Tag(i)) + if val, ok := tags.Lookup(structTag); ok { + if equalFieldName(val, name) { + if foundField != nil && foundFieldWasTag { + return nil, errors.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", structTag, val) + } + + foundField = field + foundFieldWasTag = true + } + } + } + + if field.Anonymous() { + + fieldType := field.Type() + + if ptr, ok := fieldType.(*types.Pointer); ok { + fieldType = ptr.Elem() + } + + // Type.Underlying() returns itself for all types except types.Named, where it returns a struct type. + // It should be safe to always call. + if named, ok := fieldType.Underlying().(*types.Struct); ok { + f, err := findField(named, name, structTag) + if err != nil && !strings.HasPrefix(err.Error(), "no field named") { + return nil, err + } + if f != nil && foundField == nil { + foundField = f + } + } + } + + if !field.Exported() { + continue + } + + if equalFieldName(field.Name(), name) && foundField == nil { // aqui! + foundField = field + } + } + + if foundField == nil { + return nil, fmt.Errorf("no field named %s", name) + } + + return foundField, nil +} + +func resolvePkg(pkgName string) (string, error) { + cwd, _ := os.Getwd() + + pkg, err := build.Default.Import(pkgName, cwd, build.FindOnly) + if err != nil { + return "", err + } + + return pkg.ImportPath, nil +} + +var keywords = []string{ + "break", + "default", + "func", + "interface", + "select", + "case", + "defer", + "go", + "map", + "struct", + "chan", + "else", + "goto", + "package", + "switch", + "const", + "fallthrough", + "if", + "range", + "type", + "continue", + "for", + "import", + "return", + "var", +} + +// sanitizeArgName prevents collisions with go keywords for arguments to resolver functions +func sanitizeArgName(name string) string { + for _, k := range keywords { + if name == k { + return name + "Arg" + } + } + return name +} diff --git a/example/starwars/models_gen.go b/example/starwars/models_gen.go index 07e36ad6f7..e418cd89e4 100644 --- a/example/starwars/models_gen.go +++ b/example/starwars/models_gen.go @@ -12,6 +12,10 @@ type Character interface { IsCharacter() } +type SearchResult interface { + IsSearchResult() +} + type FriendsEdge struct { Cursor string `json:"cursor"` Node Character `json:"node"` @@ -23,10 +27,6 @@ type PageInfo struct { HasNextPage bool `json:"hasNextPage"` } -type SearchResult interface { - IsSearchResult() -} - type Starship struct { ID string `json:"id"` Name string `json:"name"`