diff --git a/codegen/build.go b/codegen/build.go index 588ca01d234..82ee4da7dcd 100644 --- a/codegen/build.go +++ b/codegen/build.go @@ -1,22 +1,20 @@ package codegen import ( + "fmt" "go/types" "sort" - "fmt" - "github.com/99designs/gqlgen/codegen/config" "github.com/pkg/errors" "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/loader" ) type builder struct { Config *config.Config Schema *ast.Schema SchemaStr map[string]string - Program *loader.Program + Binder *config.Binder Directives map[string]*Directive NamedTypes NamedTypes } @@ -37,10 +35,9 @@ func buildSchema(cfg *config.Config) (*Schema, error) { return nil, err } - progLoader := b.Config.NewLoaderWithoutErrors() - b.Program, err = progLoader.Load() + b.Binder, err = b.Config.NewBinder() if err != nil { - return nil, errors.Wrap(err, "loading failed") + return nil, err } b.NamedTypes = NamedTypes{} @@ -120,7 +117,7 @@ func (b *builder) injectIntrospectionRoots(s *Schema) error { return fmt.Errorf("root query type must be defined") } - typeType, err := b.FindGoType("github.com/99designs/gqlgen/graphql/introspection", "Type") + typeType, err := b.Binder.FindObject("github.com/99designs/gqlgen/graphql/introspection", "Type") if err != nil { return errors.Wrap(err, "unable to find root Type introspection type") } @@ -145,7 +142,7 @@ func (b *builder) injectIntrospectionRoots(s *Schema) error { Object: obj, }) - schemaType, err := b.FindGoType("github.com/99designs/gqlgen/graphql/introspection", "Schema") + schemaType, err := b.Binder.FindObject("github.com/99designs/gqlgen/graphql/introspection", "Schema") if err != nil { return errors.Wrap(err, "unable to find root Schema introspection type") } @@ -161,33 +158,3 @@ func (b *builder) injectIntrospectionRoots(s *Schema) error { return nil } - -func (b *builder) FindGoType(pkgName string, typeName string) (types.Object, error) { - if pkgName == "" { - return nil, nil - } - fullName := typeName - if pkgName != "" { - fullName = pkgName + "." + typeName - } - - pkgName, err := resolvePkg(pkgName) - if err != nil { - return nil, errors.Errorf("unable to resolve package for %s: %s\n", fullName, err.Error()) - } - - pkg := b.Program.Imported[pkgName] - if pkg == nil { - return nil, errors.Errorf("required package was not loaded: %s", fullName) - } - - for astNode, def := range pkg.Defs { - if astNode.Name != typeName || def.Parent() == nil || def.Parent() != pkg.Pkg.Scope() { - continue - } - - return def, nil - } - - return nil, errors.Errorf("unable to find type %s\n", fullName) -} diff --git a/codegen/build_object.go b/codegen/build_object.go index 8e57651a685..2a66a2fc8dc 100644 --- a/codegen/build_object.go +++ b/codegen/build_object.go @@ -5,6 +5,8 @@ import ( "log" "strings" + "github.com/99designs/gqlgen/codegen/templates" + "github.com/pkg/errors" "github.com/vektah/gqlparser/ast" ) @@ -79,7 +81,7 @@ func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, e TypeReference: b.NamedTypes.getType(field.Type), Object: obj, Directives: dirs, - GoFieldName: lintName(ucFirst(field.Name)), + GoFieldName: templates.ToGo(field.Name), GoFieldType: GoFieldVariable, GoReceiverName: "obj", } @@ -99,7 +101,7 @@ func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, e f.IsResolver = true } if typeField.FieldName != "" { - f.GoFieldName = lintName(ucFirst(typeField.FieldName)) + f.GoFieldName = templates.ToGo(typeField.FieldName) } } } diff --git a/codegen/build_typedef.go b/codegen/build_typedef.go index 44b6eafbda7..9bfb272b7cd 100644 --- a/codegen/build_typedef.go +++ b/codegen/build_typedef.go @@ -44,13 +44,16 @@ func (b *builder) buildTypeDef(schemaType *ast.Definition) (*TypeDefinition, err } // External marshal functions - def, _ := b.FindGoType(pkgName, "Marshal"+typeName) + def, err := b.Binder.FindObject(pkgName, typeName) + if err != nil { + return nil, err + } if f, isFunc := def.(*types.Func); isFunc { sig := def.Type().(*types.Signature) t.GoType = sig.Params().At(0).Type() t.Marshaler = f - unmarshal, err := b.FindGoType(pkgName, "Unmarshal"+typeName) + unmarshal, err := b.Binder.FindObject(pkgName, "Unmarshal"+typeName) if err != nil { return nil, errors.Wrapf(err, "unable to find unmarshal func for %s.%s", pkgName, typeName) } @@ -59,13 +62,9 @@ func (b *builder) buildTypeDef(schemaType *ast.Definition) (*TypeDefinition, err } // Normal object binding - obj, err := b.FindGoType(pkgName, typeName) - if err != nil { - return nil, errors.Wrapf(err, "unable to find %s.%s", pkgName, typeName) - } - t.GoType = obj.Type() + t.GoType = def.Type() - namedType := obj.Type().(*types.Named) + namedType := def.Type().(*types.Named) hasUnmarshal := false for i := 0; i < namedType.NumMethods(); i++ { switch namedType.Method(i).Name() { diff --git a/codegen/config/binder.go b/codegen/config/binder.go new file mode 100644 index 00000000000..9adb868e8cb --- /dev/null +++ b/codegen/config/binder.go @@ -0,0 +1,98 @@ +package config + +import ( + "fmt" + "go/types" + "regexp" + "strings" + + "github.com/pkg/errors" + "golang.org/x/tools/go/loader" +) + +// Binder connects graphql types to golang types using static analysis +type Binder struct { + program *loader.Program + types TypeMap +} + +func (c *Config) NewBinder() (*Binder, error) { + conf := loader.Config{ + AllowErrors: true, + TypeChecker: types.Config{ + Error: func(e error) {}, + }, + } + + for _, pkg := range c.Models.ReferencedPackages() { + conf.Import(pkg) + } + + prog, err := conf.Load() + if err != nil { + return nil, errors.Wrap(err, "loading program") + } + + return &Binder{ + program: prog, + types: c.Models, + }, nil +} + +func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) { + obj, err := b.FindObject(pkgName, typeName) + if err != nil { + return nil, err + } + + if fun, isFunc := obj.(*types.Func); isFunc { + return fun.Type().(*types.Signature).Params().At(0).Type(), nil + } + return obj.Type(), nil +} + +func (b *Binder) getPkg(find string) *loader.PackageInfo { + for n, p := range b.program.Imported { + if normalizeVendor(find) == normalizeVendor(n) { + return p + } + } + return nil +} + +func (b *Binder) FindObject(pkgName string, typeName string) (types.Object, error) { + if pkgName == "" { + return nil, fmt.Errorf("package cannot be nil") + } + fullName := typeName + if pkgName != "" { + fullName = pkgName + "." + typeName + } + + pkg := b.getPkg(pkgName) + if pkg == nil { + return nil, errors.Errorf("required package was not loaded: %s", fullName) + } + + for astNode, def := range pkg.Defs { + // only look at defs in the top scope + if def == nil || def.Parent() == nil || def.Parent() != pkg.Pkg.Scope() { + continue + } + + if astNode.Name == typeName || astNode.Name == "Marshal"+typeName { + return def, nil + } + } + + return nil, errors.Errorf("unable to find type %s\n", fullName) +} + +var modsRegex = regexp.MustCompile(`^(\*|\[\])*`) + +func normalizeVendor(pkg string) string { + modifiers := modsRegex.FindAllString(pkg, 1)[0] + pkg = strings.TrimPrefix(pkg, modifiers) + parts := strings.Split(pkg, "/vendor/") + return modifiers + parts[len(parts)-1] +} diff --git a/codegen/config/config.go b/codegen/config/config.go index fb6c9522a82..1c05fcf3186 100644 --- a/codegen/config/config.go +++ b/codegen/config/config.go @@ -238,6 +238,12 @@ func (tm TypeMap) ReferencedPackages() []string { return pkgs } +func (tm TypeMap) Add(gqlName string, goType string) { + modelCfg := tm[gqlName] + modelCfg.Model = goType + tm[gqlName] = modelCfg +} + func inStrSlice(haystack []string, needle string) bool { for _, v := range haystack { if needle == v { @@ -325,6 +331,15 @@ func (c *Config) normalize() error { return nil } +func (c *TypeMapEntry) PkgAndType() (string, string) { + parts := strings.Split(c.Model, ".") + if len(parts) == 1 { + return "", c.Model + } + + return normalizeVendor(strings.Join(parts[:len(parts)-1], ".")), parts[len(parts)-1] +} + func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) { schemaStrings := map[string]string{} diff --git a/codegen/config/loader.go b/codegen/config/loader.go deleted file mode 100644 index 2fa9013cea0..00000000000 --- a/codegen/config/loader.go +++ /dev/null @@ -1,25 +0,0 @@ -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/object.go b/codegen/object.go index d35e39eacf3..67c04c7915c 100644 --- a/codegen/object.go +++ b/codegen/object.go @@ -132,7 +132,7 @@ func (f *Field) IsConcurrent() bool { } func (f *Field) GoNameUnexported() string { - return lintName(f.GQLName) + return templates.ToGoPrivate(f.GQLName) } func (f *Field) ShortInvocation() string { @@ -353,117 +353,3 @@ func ucFirst(s string) string { r[0] = unicode.ToUpper(r[0]) return string(r) } - -// copy from https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679 - -// lintName returns a different name if it should be different. -func lintName(name string) (should string) { - // Fast path for simple cases: "_" and all lowercase. - if name == "_" { - return name - } - allLower := true - for _, r := range name { - if !unicode.IsLower(r) { - allLower = false - break - } - } - if allLower { - return name - } - - // Split camelCase at any lower->upper transition, and split on underscores. - // Check each word for common initialisms. - runes := []rune(name) - w, i := 0, 0 // index of start of word, scan - for i+1 <= len(runes) { - eow := false // whether we hit the end of a word - if i+1 == len(runes) { - eow = true - } else if runes[i+1] == '_' { - // underscore; shift the remainder forward over any run of underscores - eow = true - n := 1 - for i+n+1 < len(runes) && runes[i+n+1] == '_' { - n++ - } - - // Leave at most one underscore if the underscore is between two digits - if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { - n-- - } - - copy(runes[i+1:], runes[i+n+1:]) - runes = runes[:len(runes)-n] - } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { - // lower->non-lower - eow = true - } - i++ - if !eow { - continue - } - - // [w,i) is a word. - word := string(runes[w:i]) - if u := strings.ToUpper(word); commonInitialisms[u] { - // Keep consistent case, which is lowercase only at the start. - if w == 0 && unicode.IsLower(runes[w]) { - u = strings.ToLower(u) - } - // All the common initialisms are ASCII, - // so we can replace the bytes exactly. - copy(runes[w:], []rune(u)) - } else if w > 0 && strings.ToLower(word) == word { - // already all lowercase, and not the first word, so uppercase the first character. - runes[w] = unicode.ToUpper(runes[w]) - } - w = i - } - return string(runes) -} - -// commonInitialisms is a set of common initialisms. -// Only add entries that are highly unlikely to be non-initialisms. -// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. -var commonInitialisms = map[string]bool{ - "ACL": true, - "API": true, - "ASCII": true, - "CPU": true, - "CSS": true, - "DNS": true, - "EOF": true, - "GUID": true, - "HTML": true, - "HTTP": true, - "HTTPS": true, - "ID": true, - "IP": true, - "JSON": true, - "LHS": true, - "QPS": true, - "RAM": true, - "RHS": true, - "RPC": true, - "SLA": true, - "SMTP": true, - "SQL": true, - "SSH": true, - "TCP": true, - "TLS": true, - "TTL": true, - "UDP": true, - "UI": true, - "UID": true, - "UUID": true, - "URI": true, - "URL": true, - "UTF8": true, - "VM": true, - "XML": true, - "XMPP": true, - "XSRF": true, - "XSS": true, -} diff --git a/codegen/templates/data.go b/codegen/templates/data.go deleted file mode 100644 index 4e4739df4ab..00000000000 --- a/codegen/templates/data.go +++ /dev/null @@ -1,13 +0,0 @@ -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 := .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 := .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 d275aac920c..fc84c820fbf 100644 --- a/codegen/templates/generated.gotpl +++ b/codegen/templates/generated.gotpl @@ -77,7 +77,7 @@ type ComplexityRoot struct { {{ range $field := $object.Fields -}} {{ if $field.Args }} func (e *executableSchema){{ $field.ArgsFunc }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - {{ template "args.gotpl" $field.Args }} + {{ render "args.gotpl" $field.Args }} } {{ end }} {{ end }} @@ -86,7 +86,7 @@ type ComplexityRoot struct { {{ range $directive := .Directives }} {{ if $directive.Args }} func (e *executableSchema){{ $directive.ArgsFunc }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - {{ template "args.gotpl" $directive.Args }} + {{ render "args.gotpl" $directive.Args }} } {{ end }} {{ end }} @@ -210,19 +210,19 @@ type executionContext struct { } {{- range $object := .Objects }} - {{ template "object.gotpl" $object }} + {{ render "object.gotpl" $object }} {{- range $field := $object.Fields }} - {{ template "field.gotpl" $field }} + {{ render "field.gotpl" $field }} {{ end }} {{- end}} {{- range $interface := .Interfaces }} - {{ template "interface.gotpl" $interface }} + {{ render "interface.gotpl" $interface }} {{- end }} {{- range $input := .Inputs }} - {{ template "input.gotpl" $input }} + {{ render "input.gotpl" $input }} {{- end }} func (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) { diff --git a/codegen/templates/inliner/inliner.go b/codegen/templates/inliner/inliner.go deleted file mode 100644 index 3c5a0a81d01..00000000000 --- a/codegen/templates/inliner/inliner.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "bytes" - "go/format" - "io/ioutil" - "strconv" - "strings" -) - -func main() { - dir := "./" - - files, err := ioutil.ReadDir(dir) - if err != nil { - panic(err) - } - - out := bytes.Buffer{} - out.WriteString("package templates\n\n") - out.WriteString("var data = map[string]string{\n") - - for _, f := range files { - if !strings.HasSuffix(f.Name(), ".gotpl") { - continue - } - - b, err := ioutil.ReadFile(dir + f.Name()) - if err != nil { - panic(err) - } - - out.WriteString(strconv.Quote(f.Name())) - out.WriteRune(':') - out.WriteString(strconv.Quote(string(b))) - out.WriteString(",\n") - } - - out.WriteString("}\n") - - formatted, err2 := format.Source(out.Bytes()) - if err2 != nil { - panic(err2) - } - - ioutil.WriteFile(dir+"data.go", formatted, 0644) -} diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index e45be8b7fba..770636598b5 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -1,22 +1,20 @@ -//go:generate go run ./inliner/inliner.go - package templates import ( "bytes" "fmt" + "go/types" "io/ioutil" "os" "path/filepath" "reflect" + "runtime" "sort" "strconv" "strings" "text/template" "unicode" - "go/types" - "github.com/99designs/gqlgen/internal/imports" "github.com/pkg/errors" ) @@ -24,6 +22,29 @@ import ( // this is done with a global because subtemplates currently get called in functions. Lets aim to remove this eventually. var CurrentImports *Imports +func RenderToFile(tpl string, destFile string, data interface{}) error { + if tpl == "" { + return fmt.Errorf("no template name given") + } + if CurrentImports != nil { + panic(fmt.Errorf("recursive or concurrent call to RenderToFile detected")) + } + CurrentImports = &Imports{destDir: filepath.Dir(destFile)} + + filename := resolveName(tpl, 1) + + var buf *bytes.Buffer + buf, err := render(filename, data) + if err != nil { + return errors.Wrap(err, destFile) + } + + b := bytes.Replace(buf.Bytes(), []byte("%%%IMPORTS%%%"), []byte(CurrentImports.String()), -1) + CurrentImports = nil + + return write(destFile, b) +} + func Funcs() template.FuncMap { return template.FuncMap{ "ucFirst": ucFirst, @@ -38,28 +59,12 @@ func Funcs() template.FuncMap { "notNil": notNil, "reserveImport": CurrentImports.Reserve, "lookupImport": CurrentImports.Lookup, + "render": func(filename string, tpldata interface{}) (*bytes.Buffer, error) { + return render(resolveName(filename, 0), tpldata) + }, } } -func Run(name string, tpldata interface{}) (*bytes.Buffer, error) { - t := template.New("").Funcs(Funcs()) - - for filename, data := range data { - _, err := t.New(filename).Parse(data) - if err != nil { - panic(err) - } - } - - buf := &bytes.Buffer{} - err := t.Lookup(name).Execute(buf, tpldata) - if err != nil { - return nil, err - } - - return buf, nil -} - func ucFirst(s string) string { if s == "" { return "" @@ -128,6 +133,126 @@ func ToCamel(s string) string { return string(buffer) } +func ToGo(name string) string { + return lintName(ToCamel(name)) +} + +func ToGoPrivate(name string) string { + return lintName(lcFirst(ToCamel(name))) +} + +// copy from https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679 +func lintName(name string) string { + // Fast path for simple cases: "_" and all lowercase. + if name == "_" { + return name + } + allLower := true + for _, r := range name { + if !unicode.IsLower(r) { + allLower = false + break + } + } + if allLower { + return name + } + + // Split camelCase at any lower->upper transition, and split on underscores. + // Check each word for common initialisms. + runes := []rune(name) + w, i := 0, 0 // index of start of word, scan + for i+1 <= len(runes) { + eow := false // whether we hit the end of a word + if i+1 == len(runes) { + eow = true + } else if runes[i+1] == '_' { + // underscore; shift the remainder forward over any run of underscores + eow = true + n := 1 + for i+n+1 < len(runes) && runes[i+n+1] == '_' { + n++ + } + + // Leave at most one underscore if the underscore is between two digits + if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { + n-- + } + + copy(runes[i+1:], runes[i+n+1:]) + runes = runes[:len(runes)-n] + } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + // lower->non-lower + eow = true + } + i++ + if !eow { + continue + } + + // [w,i) is a word. + word := string(runes[w:i]) + if u := strings.ToUpper(word); commonInitialisms[u] { + // Keep consistent case, which is lowercase only at the start. + if w == 0 && unicode.IsLower(runes[w]) { + u = strings.ToLower(u) + } + // All the common initialisms are ASCII, + // so we can replace the bytes exactly. + copy(runes[w:], []rune(u)) + } else if w > 0 && strings.ToLower(word) == word { + // already all lowercase, and not the first word, so uppercase the first character. + runes[w] = unicode.ToUpper(runes[w]) + } + w = i + } + return string(runes) +} + +// commonInitialisms is a set of common initialisms. +// Only add entries that are highly unlikely to be non-initialisms. +// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. +var commonInitialisms = map[string]bool{ + "ACL": true, + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SQL": true, + "SSH": true, + "TCP": true, + "TLS": true, + "TTL": true, + "UDP": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XMPP": true, + "XSRF": true, + "XSS": true, +} + func rawQuote(s string) string { return "`" + strings.Replace(s, "`", "`+\"`\"+`", -1) + "`" } @@ -194,22 +319,33 @@ func prefixLines(prefix, s string) string { return prefix + strings.Replace(s, "\n", "\n"+prefix, -1) } -func RenderToFile(tpl string, filename string, data interface{}) error { - if CurrentImports != nil { - panic(fmt.Errorf("recursive or concurrent call to RenderToFile detected")) +func resolveName(name string, skip int) string { + if name[0] == '.' { + // load path relative to calling source file + _, callerFile, _, _ := runtime.Caller(skip + 1) + return filepath.Join(filepath.Dir(callerFile), name[1:]) } - CurrentImports = &Imports{destDir: filepath.Dir(filename)} - var buf *bytes.Buffer - buf, err := Run(tpl, data) + // load path relative to this directory + _, callerFile, _, _ := runtime.Caller(0) + return filepath.Join(filepath.Dir(callerFile), name) +} + +func render(filename string, tpldata interface{}) (*bytes.Buffer, error) { + t := template.New("").Funcs(Funcs()) + + b, err := ioutil.ReadFile(filename) if err != nil { - return errors.Wrap(err, filename) + return nil, err } - b := bytes.Replace(buf.Bytes(), []byte("%%%IMPORTS%%%"), []byte(CurrentImports.String()), -1) - CurrentImports = nil + t, err = t.New(filepath.Base(filename)).Parse(string(b)) + if err != nil { + panic(err) + } - return write(filename, b) + buf := &bytes.Buffer{} + return buf, t.Execute(buf, tpldata) } func write(filename string, b []byte) error { diff --git a/codegen/util.go b/codegen/util.go index c06801cdbe9..a5529c81e3f 100644 --- a/codegen/util.go +++ b/codegen/util.go @@ -1,9 +1,7 @@ package codegen import ( - "go/build" "go/types" - "os" "strings" "github.com/pkg/errors" @@ -48,17 +46,6 @@ func equalFieldName(source, target string) bool { return strings.EqualFold(source, target) } -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", diff --git a/example/todo/models_gen.go b/example/todo/models_gen.go index 6abc2273493..74a1c0da57b 100644 --- a/example/todo/models_gen.go +++ b/example/todo/models_gen.go @@ -10,8 +10,10 @@ import ( // Passed to createTodo to create a new todo type TodoInput struct { + // The body text Text string `json:"text"` - Done *bool `json:"done"` + // Is it done already? + Done *bool `json:"done"` } type Role string diff --git a/generate.go b/generate.go index b339fd7b380..c93dcbca634 100644 --- a/generate.go +++ b/generate.go @@ -6,24 +6,35 @@ import ( "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/plugin" + "github.com/99designs/gqlgen/plugin/modelgen" "github.com/pkg/errors" + "golang.org/x/tools/go/loader" ) -func Generate(cfg *config.Config) error { +func Generate(cfg *config.Config, option ...Option) error { _ = syscall.Unlink(cfg.Exec.Filename) _ = syscall.Unlink(cfg.Model.Filename) - schema, err := codegen.NewSchema(cfg) - if err != nil { - return errors.Wrap(err, "merging failed") + plugins := []plugin.Plugin{ + modelgen.New(), } - if err = buildModels(schema); err != nil { - return errors.Wrap(err, "generating models failed") + for _, o := range option { + o(cfg, &plugins) + } + + for _, p := range plugins { + if mut, ok := p.(plugin.ConfigMutator); ok { + err := mut.MutateConfig(cfg) + if err != nil { + return errors.Wrap(err, p.Name()) + } + } } // Merge again now that the generated models have been injected into the typemap - schema, err = codegen.NewSchema(schema.Config) + schema, err := codegen.NewSchema(cfg) if err != nil { return errors.Wrap(err, "merging failed") } @@ -46,8 +57,18 @@ func Generate(cfg *config.Config) error { } func validate(cfg *config.Config) error { - progLoader := cfg.NewLoaderWithErrors() - _, err := progLoader.Load() + conf := loader.Config{} + + conf.Import(cfg.Exec.ImportPath()) + if cfg.Model.IsDefined() { + conf.Import(cfg.Model.ImportPath()) + } + + if cfg.Resolver.IsDefined() { + conf.Import(cfg.Resolver.ImportPath()) + } + + _, err := conf.Load() return err } diff --git a/model.go b/model.go deleted file mode 100644 index ec0dd65f677..00000000000 --- a/model.go +++ /dev/null @@ -1,66 +0,0 @@ -package gqlgen - -import ( - "sort" - - "go/types" - - "github.com/99designs/gqlgen/codegen" - "github.com/99designs/gqlgen/codegen/templates" -) - -type ModelBuild struct { - Interfaces []*codegen.Interface - Models codegen.Objects - Enums []codegen.Enum - PackageName string -} - -// Create a list of models that need to be generated -func buildModels(s *codegen.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/option.go b/option.go new file mode 100644 index 00000000000..0c173c7264e --- /dev/null +++ b/option.go @@ -0,0 +1,20 @@ +package gqlgen + +import ( + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/plugin" +) + +type Option func(cfg *config.Config, plugins *[]plugin.Plugin) + +func NoPlugins() Option { + return func(cfg *config.Config, plugins *[]plugin.Plugin) { + *plugins = nil + } +} + +func AddPlugin(p plugin.Plugin) Option { + return func(cfg *config.Config, plugins *[]plugin.Plugin) { + *plugins = append(*plugins, p) + } +} diff --git a/plugin/modelgen/models.go b/plugin/modelgen/models.go new file mode 100644 index 00000000000..c6bfa329fd0 --- /dev/null +++ b/plugin/modelgen/models.go @@ -0,0 +1,193 @@ +package modelgen + +import ( + "go/types" + "sort" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/plugin" + "github.com/vektah/gqlparser/ast" +) + +type ModelBuild struct { + PackageName string + Interfaces []*Interface + Models []*Object + Enums []*Enum +} + +type Interface struct { + Description string + Name string +} + +type Object struct { + Description string + Name string + Fields []*Field + Implements []string +} + +type Field struct { + Description string + Name string + Type types.Type + Tag string +} + +type Enum struct { + Description string + Name string + Raw string + Values []*EnumValue +} + +type EnumValue struct { + Description string + Name string + Value string +} + +func New() plugin.Plugin { + return &Plugin{} +} + +type Plugin struct{} + +var _ plugin.ConfigMutator = &Plugin{} + +func (m *Plugin) Name() string { + return "modelgen" +} + +func (m *Plugin) MutateConfig(cfg *config.Config) error { + if err := cfg.Check(); err != nil { + return err + } + + schema, _, err := cfg.LoadSchema() + if err != nil { + return err + } + + binder, err := cfg.NewBinder() + if err != nil { + return err + } + + b := &ModelBuild{ + PackageName: cfg.Model.Package, + } + + for _, schemaType := range schema.Types { + if cfg.Models.UserDefined(schemaType.Name) { + continue + } + + switch schemaType.Kind { + case ast.Interface, ast.Union: + it := &Interface{ + Description: schemaType.Description, + Name: templates.ToGo(schemaType.Name), + } + + b.Interfaces = append(b.Interfaces, it) + case ast.Object, ast.InputObject: + if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription { + continue + } + it := &Object{ + Description: schemaType.Description, + Name: templates.ToGo(schemaType.Name), + } + + for _, implementor := range schema.GetImplements(schemaType) { + it.Implements = append(it.Implements, templates.ToGo(implementor.Name)) + } + + for _, field := range schemaType.Fields { + var typ types.Type + + if cfg.Models.UserDefined(field.Type.Name()) { + model := cfg.Models[field.Type.Name()] + pkg, typeName := model.PkgAndType() + typ, err = binder.FindType(pkg, typeName) + if err != nil { + return err + } + } else { + // no user defined model, must reference another generated model + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), + nil, + nil, + ) + } + + name := field.Name + if nameOveride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOveride != "" { + name = nameOveride + } + + fd := schema.Types[field.Type.Name()] + it.Fields = append(it.Fields, &Field{ + Name: templates.ToGo(name), + Type: copyModifiersFromAst(field.Type, fd.Kind != ast.Interface, typ), + Description: field.Description, + Tag: `json:"` + field.Name + `"`, + }) + } + + b.Models = append(b.Models, it) + case ast.Enum: + it := &Enum{ + Name: templates.ToGo(schemaType.Name), + Raw: schemaType.Name, + Description: schemaType.Description, + } + + for _, v := range schemaType.EnumValues { + it.Values = append(it.Values, &EnumValue{ + Name: templates.ToGo(v.Name), + Value: v.Name, + Description: v.Description, + }) + } + + b.Enums = append(b.Enums, it) + } + } + + sort.Slice(b.Enums, func(i, j int) bool { return b.Enums[i].Name < b.Enums[j].Name }) + sort.Slice(b.Models, func(i, j int) bool { return b.Models[i].Name < b.Models[j].Name }) + sort.Slice(b.Interfaces, func(i, j int) bool { return b.Interfaces[i].Name < b.Interfaces[j].Name }) + + for _, it := range b.Enums { + cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+it.Name) + } + for _, it := range b.Models { + cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+it.Name) + } + for _, it := range b.Interfaces { + cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+it.Name) + } + + if len(b.Models) == 0 && len(b.Enums) == 0 { + return nil + } + + return templates.RenderToFile("./models.gotpl", cfg.Model.Filename, b) +} + +func copyModifiersFromAst(t *ast.Type, usePtr bool, base types.Type) types.Type { + if t.Elem != nil { + return types.NewSlice(copyModifiersFromAst(t.Elem, usePtr, base)) + } + + if !t.NonNull && usePtr { + return types.NewPointer(base) + } + + return base +} diff --git a/codegen/templates/models.gotpl b/plugin/modelgen/models.gotpl similarity index 50% rename from codegen/templates/models.gotpl rename to plugin/modelgen/models.gotpl index 8e1071e8c55..a587b281f72 100644 --- a/codegen/templates/models.gotpl +++ b/plugin/modelgen/models.gotpl @@ -21,72 +21,72 @@ import ( ) {{- range $model := .Interfaces }} - {{with .Definition.GQLDefinition.Description }} {{.|prefixLines "// "}} {{end}} - type {{.Definition.GoType | ref }} interface { - Is{{.Definition.GoType | ref }}() + {{ with .Description }} {{.|prefixLines "// "}} {{ end }} + type {{.Name }} interface { + Is{{.Name }}() } {{- end }} {{ range $model := .Models }} - {{with .Definition.GQLDefinition.Description }} {{.|prefixLines "// "}} {{end}} - type {{.Definition.GoType | ref }} struct { + {{with .Description }} {{.|prefixLines "// "}} {{end}} + type {{ .Name }} struct { {{- range $field := .Fields }} - {{- with .Definition.GQLDefinition.Description }} + {{- with .Description }} {{.|prefixLines "// "}} {{- end}} - {{ $field.GoFieldName }} {{$field.GoType | ref}} `json:"{{$field.GQLName}}"` + {{ $field.Name }} {{$field.Type | ref}} `{{$field.Tag}}` {{- end }} } {{- range $iface := .Implements }} - func ({{$model.Definition.GoType | ref }}) Is{{$iface.GoType | ref }}() {} + func ({{ $model.Name }}) Is{{ $iface }}() {} {{- end }} {{- end}} {{ range $enum := .Enums }} - {{with .Definition.GQLDefinition.Description }} {{.|prefixLines "// "}} {{end}} - type {{.Definition.GoType | ref }} string + {{ with .Description }} {{.|prefixLines "// "}} {{end}} + type {{.Name }} string const ( {{- range $value := .Values}} {{- with .Description}} {{.|prefixLines "// "}} {{- end}} - {{$enum.Definition.GoType | ref }}{{ .Name|toCamel }} {{$enum.Definition.GoType | ref }} = {{.Name|quote}} + {{ $enum.Name }}{{ .Name }} {{$enum.Name }} = {{.Value|quote}} {{- end }} ) - var All{{.Definition.GoType | ref }} = []{{.Definition.GoType | ref }}{ + var All{{.Name }} = []{{ .Name }}{ {{- range $value := .Values}} - {{$enum.Definition.GoType | ref }}{{ .Name|toCamel }}, + {{$enum.Name }}{{ .Name }}, {{- end }} } - func (e {{.Definition.GoType | ref }}) IsValid() bool { + func (e {{.Name }}) IsValid() bool { switch e { - case {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.Definition.GoType | ref }}{{ $element.Name|toCamel }}{{end}}: + case {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.Name }}{{ $element.Name }}{{end}}: return true } return false } - func (e {{.Definition.GoType | ref }}) String() string { + func (e {{.Name }}) String() string { return string(e) } - func (e *{{.Definition.GoType | ref }}) UnmarshalGQL(v interface{}) error { + func (e *{{.Name }}) UnmarshalGQL(v interface{}) error { str, ok := v.(string) if !ok { return fmt.Errorf("enums must be strings") } - *e = {{.Definition.GoType | ref }}(str) + *e = {{ .Name }}(str) if !e.IsValid() { - return fmt.Errorf("%s is not a valid {{.Definition.GQLDefinition.Name}}", str) + return fmt.Errorf("%s is not a valid {{ .Raw }}", str) } return nil } - func (e {{.Definition.GoType | ref }}) MarshalGQL(w io.Writer) { + func (e {{.Name }}) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } diff --git a/plugin/modelgen/models_test.go b/plugin/modelgen/models_test.go new file mode 100644 index 00000000000..49ea8c16ac2 --- /dev/null +++ b/plugin/modelgen/models_test.go @@ -0,0 +1,20 @@ +package modelgen + +import ( + "testing" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/stretchr/testify/require" +) + +func TestModelGeneration(t *testing.T) { + cfg, err := config.LoadConfig("testdata/gqlgen.yml") + require.NoError(t, err) + p := Plugin{} + require.NoError(t, p.MutateConfig(cfg)) + + require.True(t, cfg.Models.UserDefined("MissingType")) + require.True(t, cfg.Models.UserDefined("MissingEnum")) + require.True(t, cfg.Models.UserDefined("MissingUnion")) + require.True(t, cfg.Models.UserDefined("MissingInterface")) +} diff --git a/plugin/modelgen/out/existing.go b/plugin/modelgen/out/existing.go new file mode 100644 index 00000000000..6e91b1eeb8b --- /dev/null +++ b/plugin/modelgen/out/existing.go @@ -0,0 +1,23 @@ +package out + +type ExistingModel struct { + Name string + Enum ExistingEnum + Int ExistingInterface +} + +type ExistingInput struct { + Name string + Enum ExistingEnum + Int ExistingInterface +} + +type ExistingEnum string + +type ExistingInterface interface { + IsExistingInterface() +} + +type ExistingUnion interface { + IsExistingUnion() +} diff --git a/plugin/modelgen/out/generated.go b/plugin/modelgen/out/generated.go new file mode 100644 index 00000000000..a3f910516aa --- /dev/null +++ b/plugin/modelgen/out/generated.go @@ -0,0 +1,89 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package out + +import ( + "fmt" + "io" + "strconv" +) + +type MissingInterface interface { + IsMissingInterface() +} + +type MissingUnion interface { + IsMissingUnion() +} + +type ExistingType struct { + Name *string `json:"name"` + Enum *ExistingEnum `json:"enum"` + Int ExistingInterface `json:"int"` + Existing *MissingType `json:"existing"` +} + +func (ExistingType) IsMissingUnion() {} +func (ExistingType) IsMissingInterface() {} +func (ExistingType) IsExistingInterface() {} +func (ExistingType) IsExistingUnion() {} + +type MissingInput struct { + Name *string `json:"name"` + Enum *MissingEnum `json:"enum"` + Int MissingInterface `json:"int"` + Existing *ExistingType `json:"existing"` +} + +type MissingType struct { + Name *string `json:"name"` + Enum *MissingEnum `json:"enum"` + Int MissingInterface `json:"int"` + Existing *ExistingType `json:"existing"` +} + +func (MissingType) IsMissingInterface() {} +func (MissingType) IsExistingInterface() {} +func (MissingType) IsMissingUnion() {} +func (MissingType) IsExistingUnion() {} + +type MissingEnum string + +const ( + MissingEnumHello MissingEnum = "Hello" + MissingEnumGoodbye MissingEnum = "Goodbye" +) + +var AllMissingEnum = []MissingEnum{ + MissingEnumHello, + MissingEnumGoodbye, +} + +func (e MissingEnum) IsValid() bool { + switch e { + case MissingEnumHello, MissingEnumGoodbye: + return true + } + return false +} + +func (e MissingEnum) String() string { + return string(e) +} + +func (e *MissingEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = MissingEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid MissingEnum", str) + } + return nil +} + +func (e MissingEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/plugin/modelgen/testdata/gqlgen.yml b/plugin/modelgen/testdata/gqlgen.yml new file mode 100644 index 00000000000..b32289be0d3 --- /dev/null +++ b/plugin/modelgen/testdata/gqlgen.yml @@ -0,0 +1,21 @@ +schema: + - "testdata/schema.graphql" + +exec: + filename: out/generated.go +model: + filename: out/generated.go + +models: + ExistingModel: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingModel + ExistingInput: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingInput + ExistingEnum: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingEnum + ExistingInterface: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingInterface + ExistingUnion: + model: github.com/99designs/gqlgen/plugin/modelgen/out.ExistingUnion + + diff --git a/plugin/modelgen/testdata/schema.graphql b/plugin/modelgen/testdata/schema.graphql new file mode 100644 index 00000000000..6be346bdbcc --- /dev/null +++ b/plugin/modelgen/testdata/schema.graphql @@ -0,0 +1,62 @@ +type Query { + thisShoudlntGetGenerated: Boolean +} + +type Mutation { + thisShoudlntGetGenerated: Boolean +} + +type Subscription { + thisShoudlntGetGenerated: Boolean +} + +type MissingType implements MissingInterface & ExistingInterface { + name: String + enum: MissingEnum + int: MissingInterface + existing: ExistingType +} + +input MissingInput { + name: String + enum: MissingEnum + int: MissingInterface + existing: ExistingType +} + +enum MissingEnum { + Hello + Goodbye +} + +interface MissingInterface { + name: String +} + +union MissingUnion = MissingType | ExistingType + +type ExistingType implements MissingInterface & ExistingInterface { + name: String + enum: ExistingEnum + int: ExistingInterface + existing: MissingType +} + +input ExistingInput { + name: String + enum: ExistingEnum + int: ExistingInterface + existing: MissingType +} + +enum ExistingEnum { + Hello + Goodbye +} + +interface ExistingInterface { + name: String +} + +union ExistingUnion = MissingType | ExistingType + diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 00000000000..55492b1799f --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,13 @@ +// plugin package interfaces are EXPERIMENTAL. + +package plugin + +import "github.com/99designs/gqlgen/codegen/config" + +type Plugin interface { + Name() string +} + +type ConfigMutator interface { + MutateConfig(cfg *config.Config) error +}