Update gqlgen vendors

Amine Hilaly created

Change summary

vendor/github.com/99designs/gqlgen/api/generate.go                     |   2 
vendor/github.com/99designs/gqlgen/codegen/args.go                     |  16 
vendor/github.com/99designs/gqlgen/codegen/args.gotpl                  |  20 
vendor/github.com/99designs/gqlgen/codegen/config/binder.go            |  24 
vendor/github.com/99designs/gqlgen/codegen/config/config.go            |  96 
vendor/github.com/99designs/gqlgen/codegen/data.go                     |   7 
vendor/github.com/99designs/gqlgen/codegen/directive.go                |  63 
vendor/github.com/99designs/gqlgen/codegen/directives.gotpl            | 137 
vendor/github.com/99designs/gqlgen/codegen/field.go                    |  23 
vendor/github.com/99designs/gqlgen/codegen/field.gotpl                 |  94 
vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl            |  59 
vendor/github.com/99designs/gqlgen/codegen/input.gotpl                 |  24 
vendor/github.com/99designs/gqlgen/graphql/version.go                  |   2 
vendor/github.com/99designs/gqlgen/handler/graphql.go                  |  93 
vendor/github.com/99designs/gqlgen/handler/websocket.go                |  10 
vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go           |   5 
vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go      |   1 
vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl   |   6 
vendor/github.com/99designs/gqlgen/plugin/schemaconfig/schemaconfig.go |  93 
19 files changed, 632 insertions(+), 143 deletions(-)

Detailed changes

vendor/github.com/99designs/gqlgen/api/generate.go 🔗

@@ -8,6 +8,7 @@ import (
 	"github.com/99designs/gqlgen/plugin"
 	"github.com/99designs/gqlgen/plugin/modelgen"
 	"github.com/99designs/gqlgen/plugin/resolvergen"
+	"github.com/99designs/gqlgen/plugin/schemaconfig"
 	"github.com/pkg/errors"
 	"golang.org/x/tools/go/packages"
 )
@@ -17,6 +18,7 @@ func Generate(cfg *config.Config, option ...Option) error {
 	_ = syscall.Unlink(cfg.Model.Filename)
 
 	plugins := []plugin.Plugin{
+		schemaconfig.New(),
 		modelgen.New(),
 		resolvergen.New(),
 	}

vendor/github.com/99designs/gqlgen/codegen/args.go 🔗

@@ -26,6 +26,22 @@ type FieldArgument struct {
 	Value         interface{} // value set in Data
 }
 
+//ImplDirectives get not Builtin and location ARGUMENT_DEFINITION directive
+func (f *FieldArgument) ImplDirectives() []*Directive {
+	d := make([]*Directive, 0)
+	for i := range f.Directives {
+		if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) {
+			d = append(d, f.Directives[i])
+		}
+	}
+
+	return d
+}
+
+func (f *FieldArgument) DirectiveObjName() string {
+	return "rawArgs"
+}
+
 func (f *FieldArgument) Stream() bool {
 	return f.Object != nil && f.Object.Stream
 }

vendor/github.com/99designs/gqlgen/codegen/args.gotpl 🔗

@@ -5,22 +5,10 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
 	{{- range $i, $arg := . }}
 		var arg{{$i}} {{ $arg.TypeReference.GO | ref}}
 		if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok {
-			{{- if $arg.Directives }}
-				getArg0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) }
-
-				{{- range $i, $directive := $arg.Directives }}
-					getArg{{add $i 1}} := func(ctx context.Context) (res interface{}, err error) {
-						{{- range $dArg := $directive.Args }}
-							{{- if and $dArg.TypeReference.IsPtr ( notNil "Value" $dArg ) }}
-								{{ $dArg.VarName }} := {{ $dArg.Value | dump }}
-							{{- end }}
-						{{- end }}
-						n := getArg{{$i}}
-						return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs "tmp" "n" }})
-					}
-				{{- end }}
-
-				tmp, err = getArg{{$arg.Directives|len}}(ctx)
+			{{- if $arg.ImplDirectives }}
+				directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) }
+				{{ template "implDirectives" $arg }}
+				tmp, err = directive{{$arg.ImplDirectives|len}}(ctx)
 				if err != nil {
 					return nil, err
 				}

vendor/github.com/99designs/gqlgen/codegen/config/binder.go 🔗

@@ -14,7 +14,7 @@ import (
 
 // Binder connects graphql types to golang types using static analysis
 type Binder struct {
-	pkgs       []*packages.Package
+	pkgs       map[string]*packages.Package
 	schema     *ast.Schema
 	cfg        *Config
 	References []*TypeReference
@@ -26,7 +26,9 @@ func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
 		return nil, err
 	}
 
+	mp := map[string]*packages.Package{}
 	for _, p := range pkgs {
+		populatePkg(mp, p)
 		for _, e := range p.Errors {
 			if e.Kind == packages.ListError {
 				return nil, p.Errors[0]
@@ -35,12 +37,23 @@ func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
 	}
 
 	return &Binder{
-		pkgs:   pkgs,
+		pkgs:   mp,
 		schema: s,
 		cfg:    c,
 	}, nil
 }
 
+func populatePkg(mp map[string]*packages.Package, p *packages.Package) {
+	imp := code.NormalizeVendor(p.PkgPath)
+	if _, ok := mp[imp]; ok {
+		return
+	}
+	mp[imp] = p
+	for _, p := range p.Imports {
+		populatePkg(mp, p)
+	}
+}
+
 func (b *Binder) TypePosition(typ types.Type) token.Position {
 	named, isNamed := typ.(*types.Named)
 	if !isNamed {
@@ -75,10 +88,9 @@ func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
 }
 
 func (b *Binder) getPkg(find string) *packages.Package {
-	for _, p := range b.pkgs {
-		if code.NormalizeVendor(find) == code.NormalizeVendor(p.PkgPath) {
-			return p
-		}
+	imp := code.NormalizeVendor(find)
+	if p, ok := b.pkgs[imp]; ok {
+		return p
 	}
 	return nil
 }

vendor/github.com/99designs/gqlgen/codegen/config/config.go 🔗

@@ -6,9 +6,12 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strings"
 
+	"golang.org/x/tools/go/packages"
+
 	"github.com/99designs/gqlgen/internal/code"
 	"github.com/pkg/errors"
 	"github.com/vektah/gqlparser"
@@ -17,12 +20,14 @@ import (
 )
 
 type Config struct {
-	SchemaFilename StringList    `yaml:"schema,omitempty"`
-	Exec           PackageConfig `yaml:"exec"`
-	Model          PackageConfig `yaml:"model"`
-	Resolver       PackageConfig `yaml:"resolver,omitempty"`
-	Models         TypeMap       `yaml:"models,omitempty"`
-	StructTag      string        `yaml:"struct_tag,omitempty"`
+	SchemaFilename StringList                 `yaml:"schema,omitempty"`
+	Exec           PackageConfig              `yaml:"exec"`
+	Model          PackageConfig              `yaml:"model"`
+	Resolver       PackageConfig              `yaml:"resolver,omitempty"`
+	AutoBind       []string                   `yaml:"autobind"`
+	Models         TypeMap                    `yaml:"models,omitempty"`
+	StructTag      string                     `yaml:"struct_tag,omitempty"`
+	Directives     map[string]DirectiveConfig `yaml:"directives,omitempty"`
 }
 
 var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"}
@@ -33,6 +38,17 @@ func DefaultConfig() *Config {
 		SchemaFilename: StringList{"schema.graphql"},
 		Model:          PackageConfig{Filename: "models_gen.go"},
 		Exec:           PackageConfig{Filename: "generated.go"},
+		Directives: map[string]DirectiveConfig{
+			"skip": {
+				SkipRuntime: true,
+			},
+			"include": {
+				SkipRuntime: true,
+			},
+			"deprecated": {
+				SkipRuntime: true,
+			},
+		},
 	}
 }
 
@@ -51,6 +67,13 @@ func LoadConfigFromDefaultLocations() (*Config, error) {
 	return LoadConfig(cfgFile)
 }
 
+var path2regex = strings.NewReplacer(
+	`.`, `\.`,
+	`*`, `.+`,
+	`\`, `[\\/]`,
+	`/`, `[\\/]`,
+)
+
 // LoadConfig reads the gqlgen.yml config file
 func LoadConfig(filename string) (*Config, error) {
 	config := DefaultConfig()
@@ -67,9 +90,35 @@ func LoadConfig(filename string) (*Config, error) {
 	preGlobbing := config.SchemaFilename
 	config.SchemaFilename = StringList{}
 	for _, f := range preGlobbing {
-		matches, err := filepath.Glob(f)
-		if err != nil {
-			return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
+		var matches []string
+
+		// for ** we want to override default globbing patterns and walk all
+		// subdirectories to match schema files.
+		if strings.Contains(f, "**") {
+			pathParts := strings.SplitN(f, "**", 2)
+			rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
+			// turn the rest of the glob into a regex, anchored only at the end because ** allows
+			// for any number of dirs in between and walk will let us match against the full path name
+			globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
+
+			if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
+				if err != nil {
+					return err
+				}
+
+				if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
+					matches = append(matches, path)
+				}
+
+				return nil
+			}); err != nil {
+				return nil, errors.Wrapf(err, "failed to walk schema at root %s", pathParts[0])
+			}
+		} else {
+			matches, err = filepath.Glob(f)
+			if err != nil {
+				return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
+			}
 		}
 
 		for _, m := range matches {
@@ -265,6 +314,10 @@ func (tm TypeMap) Add(Name string, goType string) {
 	tm[Name] = modelCfg
 }
 
+type DirectiveConfig struct {
+	SkipRuntime bool `yaml:"skip_runtime"`
+}
+
 func inStrSlice(haystack []string, needle string) bool {
 	for _, v := range haystack {
 		if needle == v {
@@ -329,6 +382,31 @@ func (c *Config) normalize() error {
 	return nil
 }
 
+func (c *Config) Autobind(s *ast.Schema) error {
+	if len(c.AutoBind) == 0 {
+		return nil
+	}
+	ps, err := packages.Load(&packages.Config{Mode: packages.LoadTypes}, c.AutoBind...)
+	if err != nil {
+		return err
+	}
+
+	for _, t := range s.Types {
+		if c.Models.UserDefined(t.Name) {
+			continue
+		}
+
+		for _, p := range ps {
+			if t := p.Types.Scope().Lookup(t.Name); t != nil {
+				c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name())
+				break
+			}
+		}
+	}
+
+	return nil
+}
+
 func (c *Config) InjectBuiltins(s *ast.Schema) {
 	builtins := TypeMap{
 		"__Directive":         {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}},

vendor/github.com/99designs/gqlgen/codegen/data.go 🔗

@@ -15,7 +15,7 @@ type Data struct {
 	Config          *config.Config
 	Schema          *ast.Schema
 	SchemaStr       map[string]string
-	Directives      map[string]*Directive
+	Directives      DirectiveList
 	Objects         Objects
 	Inputs          Objects
 	Interfaces      map[string]*Interface
@@ -51,6 +51,11 @@ func BuildData(cfg *config.Config) (*Data, error) {
 		return nil, err
 	}
 
+	err = cfg.Autobind(b.Schema)
+	if err != nil {
+		return nil, err
+	}
+
 	cfg.InjectBuiltins(b.Schema)
 
 	b.Binder, err = b.Config.NewBinder(b.Schema)

vendor/github.com/99designs/gqlgen/codegen/directive.go 🔗

@@ -10,12 +10,43 @@ import (
 	"github.com/vektah/gqlparser/ast"
 )
 
+type DirectiveList map[string]*Directive
+
+//LocationDirectives filter directives by location
+func (dl DirectiveList) LocationDirectives(location string) DirectiveList {
+	return locationDirectives(dl, ast.DirectiveLocation(location))
+}
+
 type Directive struct {
+	*ast.DirectiveDefinition
 	Name    string
 	Args    []*FieldArgument
 	Builtin bool
 }
 
+//IsLocation check location directive
+func (d *Directive) IsLocation(location ...ast.DirectiveLocation) bool {
+	for _, l := range d.Locations {
+		for _, a := range location {
+			if l == a {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func locationDirectives(directives DirectiveList, location ...ast.DirectiveLocation) map[string]*Directive {
+	mDirectives := make(map[string]*Directive)
+	for name, d := range directives {
+		if d.IsLocation(location...) {
+			mDirectives[name] = d
+		}
+	}
+	return mDirectives
+}
+
 func (b *builder) buildDirectives() (map[string]*Directive, error) {
 	directives := make(map[string]*Directive, len(b.Schema.Directives))
 
@@ -24,11 +55,6 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
 			return nil, errors.Errorf("directive with name %s already exists", name)
 		}
 
-		var builtin bool
-		if name == "skip" || name == "include" || name == "deprecated" {
-			builtin = true
-		}
-
 		var args []*FieldArgument
 		for _, arg := range dir.Arguments {
 			tr, err := b.Binder.TypeReference(arg.Type, nil)
@@ -53,9 +79,10 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
 		}
 
 		directives[name] = &Directive{
-			Name:    name,
-			Args:    args,
-			Builtin: builtin,
+			DirectiveDefinition: dir,
+			Name:                name,
+			Args:                args,
+			Builtin:             b.Config.Directives[name].SkipRuntime,
 		}
 	}
 
@@ -92,8 +119,10 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) {
 			})
 		}
 		dirs[i] = &Directive{
-			Name: d.Name,
-			Args: args,
+			Name:                d.Name,
+			Args:                args,
+			DirectiveDefinition: list[i].Definition,
+			Builtin:             b.Config.Directives[d.Name].SkipRuntime,
 		}
 
 	}
@@ -119,18 +148,12 @@ func (d *Directive) CallArgs() string {
 	return strings.Join(args, ", ")
 }
 
-func (d *Directive) ResolveArgs(obj string, next string) string {
-	args := []string{"ctx", obj, next}
+func (d *Directive) ResolveArgs(obj string, next int) string {
+	args := []string{"ctx", obj, fmt.Sprintf("directive%d", next)}
 
 	for _, arg := range d.Args {
-		dArg := "&" + arg.VarName
-		if !arg.TypeReference.IsPtr() {
-			if arg.Value != nil {
-				dArg = templates.Dump(arg.Value)
-			} else {
-				dArg = templates.Dump(arg.Default)
-			}
-		} else if arg.Value == nil && arg.Default == nil {
+		dArg := arg.VarName
+		if arg.Value == nil && arg.Default == nil {
 			dArg = "nil"
 		}
 

vendor/github.com/99designs/gqlgen/codegen/directives.gotpl 🔗

@@ -0,0 +1,137 @@
+{{ define "implDirectives" }}{{ $in := .DirectiveObjName }}
+	{{- range $i, $directive := .ImplDirectives -}}
+		directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) {
+			{{- range $arg := $directive.Args }}
+				{{- if notNil "Value" $arg }}
+						{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }})
+						if err != nil{
+							return nil, err
+						}
+					{{- else if notNil "Default" $arg }}
+						{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }})
+						if err != nil{
+							return nil, err
+						}
+					{{- end }}
+			{{- end }}
+			return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs $in $i }})
+		}
+	{{- end -}}
+{{ end }}
+
+{{define "queryDirectives"}}
+	for _, d := range obj.Directives {
+		switch d.Name {
+		{{- range $directive := . }}
+		case "{{$directive.Name}}":
+			{{- if $directive.Args }}
+				rawArgs := d.ArgumentMap(ec.Variables)
+				args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
+				if err != nil {
+					ec.Error(ctx, err)
+					return graphql.Null
+				}
+			{{- end }}
+			n := next
+			next = func(ctx context.Context) (interface{}, error) {
+				return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
+			}
+		{{- end }}
+		}
+	}
+	tmp, err := next(ctx)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if data, ok := tmp.(graphql.Marshaler); ok {
+		return data
+	}
+	ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
+	return graphql.Null
+{{end}}
+
+{{ if .Directives.LocationDirectives "QUERY" }}
+func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
+	{{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }}
+}
+{{ end }}
+
+{{ if .Directives.LocationDirectives "MUTATION" }}
+func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
+	{{ template "queryDirectives" .Directives.LocationDirectives "MUTATION" }}
+}
+{{ end }}
+
+{{ if .Directives.LocationDirectives "SUBSCRIPTION" }}
+func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func() graphql.Marshaler {
+	for _, d := range obj.Directives {
+		switch d.Name {
+		{{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }}
+		case "{{$directive.Name}}":
+			{{- if $directive.Args }}
+				rawArgs := d.ArgumentMap(ec.Variables)
+				args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
+				if err != nil {
+					ec.Error(ctx, err)
+					return func() graphql.Marshaler {
+						return graphql.Null
+					}
+				}
+			{{- end }}
+			n := next
+			next = func(ctx context.Context) (interface{}, error) {
+				return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
+			}
+		{{- end }}
+		}
+	}
+	tmp, err := next(ctx)
+	if err != nil {
+		ec.Error(ctx, err)
+		return func() graphql.Marshaler {
+			return graphql.Null
+		}
+	}
+	if data, ok := tmp.(func() graphql.Marshaler); ok {
+		return data
+	}
+	ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
+	return func() graphql.Marshaler {
+		return graphql.Null
+	}
+}
+{{ end }}
+
+{{ if .Directives.LocationDirectives "FIELD" }}
+	func (ec *executionContext) _fieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) interface{} {
+		{{- if .Directives.LocationDirectives "FIELD" }}
+		rctx := graphql.GetResolverContext(ctx)
+		for _, d := range rctx.Field.Directives {
+			switch d.Name {
+			{{- range $directive := .Directives.LocationDirectives "FIELD" }}
+			case "{{$directive.Name}}":
+				{{- if $directive.Args }}
+					rawArgs := d.ArgumentMap(ec.Variables)
+					args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
+					if err != nil {
+						ec.Error(ctx, err)
+						return nil
+					}
+				{{- end }}
+				n := next
+				next = func(ctx context.Context) (interface{}, error) {
+					return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
+				}
+			{{- end }}
+			}
+		}
+		{{- end }}
+		res, err := ec.ResolverMiddleware(ctx, next)
+		if err != nil {
+			ec.Error(ctx, err)
+			return nil
+		}
+		return res
+	}
+{{ end }}

vendor/github.com/99designs/gqlgen/codegen/field.go 🔗

@@ -284,7 +284,28 @@ func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types
 }
 
 func (f *Field) HasDirectives() bool {
-	return len(f.Directives) > 0
+	return len(f.ImplDirectives()) > 0
+}
+
+func (f *Field) DirectiveObjName() string {
+	if f.Object.Root {
+		return "nil"
+	}
+	return f.GoReceiverName
+}
+
+func (f *Field) ImplDirectives() []*Directive {
+	var d []*Directive
+	loc := ast.LocationFieldDefinition
+	if f.Object.IsInputType() {
+		loc = ast.LocationInputFieldDefinition
+	}
+	for i := range f.Directives {
+		if !f.Directives[i].Builtin && f.Directives[i].IsLocation(loc) {
+			d = append(d, f.Directives[i])
+		}
+	}
+	return d
 }
 
 func (f *Field) IsReserved() bool {

vendor/github.com/99designs/gqlgen/codegen/field.gotpl 🔗

@@ -37,9 +37,15 @@
 		}
 	}
 {{ else }}
-	func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) graphql.Marshaler {
+	func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret graphql.Marshaler) {
 		ctx = ec.Tracer.StartFieldExecution(ctx, field)
-		defer func () { ec.Tracer.EndFieldExecution(ctx) }()
+		defer func () {
+			if r := recover(); r != nil {
+				ec.Error(ctx, ec.Recover(ctx, r))
+				ret = graphql.Null
+			}
+			ec.Tracer.EndFieldExecution(ctx)
+		}()
 		rctx := &graphql.ResolverContext{
 			Object: {{$object.Name|quote}},
 			Field: field,
@@ -57,31 +63,19 @@
 			rctx.Args = args
 		{{- end }}
 		ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
-		resTmp := ec.FieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
-			ctx = rctx  // use context from middleware stack in children
-			{{- if $field.IsResolver }}
-				return ec.resolvers.{{ $field.ShortInvocation }}
-			{{- else if $field.IsMap }}
-				switch v := {{$field.GoReceiverName}}[{{$field.Name|quote}}].(type) {
-				case {{$field.TypeReference.GO | ref}}:
-					return v, nil
-				case {{$field.TypeReference.Elem.GO | ref}}:
-					return &v, nil
-				case nil:
-					return ({{$field.TypeReference.GO | ref}})(nil), nil
-				default:
-					return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ $field.Name | quote}})
-				}
-			{{- else if $field.IsMethod }}
-				{{- if $field.NoErr }}
-					return {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }}), nil
-				{{- else }}
-					return {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }})
-				{{- end }}
-			{{- else if $field.IsVariable }}
-				return {{$field.GoReceiverName}}.{{$field.GoFieldName}}, nil
-			{{- end }}
-		})
+		{{- if  $.Directives.LocationDirectives "FIELD" }}
+			resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
+				{{ template "field" $field }}
+			})
+		{{ else }}
+			resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+				{{ template "field" $field }}
+			})
+			if err != nil {
+				ec.Error(ctx, err)
+				return graphql.Null
+			}
+		{{- end }}
 		if resTmp == nil {
 			{{- if $field.TypeReference.GQL.NonNull }}
 				if !ec.HasError(rctx) {
@@ -98,3 +92,49 @@
 {{ end }}
 
 {{- end }}{{- end}}
+
+{{ define "field" }}
+	{{- if .HasDirectives -}}
+		directive0 := func(rctx context.Context) (interface{}, error) {
+			ctx = rctx  // use context from middleware stack in children
+			{{ template "fieldDefinition" . }}
+		}
+		{{ template "implDirectives" . }}
+		tmp, err := directive{{.ImplDirectives|len}}(rctx)
+		if err != nil {
+			return nil, err
+		}
+		if data, ok := tmp.({{ .TypeReference.GO | ref }}) ; ok {
+			return data, nil
+		}
+		return nil, fmt.Errorf(`unexpected type %T from directive, should be {{ .TypeReference.GO }}`, tmp)
+	{{- else -}}
+		ctx = rctx  // use context from middleware stack in children
+		{{ template "fieldDefinition" . }}
+	{{- end -}}
+{{ end }}
+
+{{ define "fieldDefinition" }}
+	{{- if .IsResolver -}}
+		return ec.resolvers.{{ .ShortInvocation }}
+	{{- else if .IsMap -}}
+		switch v := {{.GoReceiverName}}[{{.Name|quote}}].(type) {
+		case {{.TypeReference.GO | ref}}:
+			return v, nil
+		case {{.TypeReference.Elem.GO | ref}}:
+			return &v, nil
+		case nil:
+			return ({{.TypeReference.GO | ref}})(nil), nil
+		default:
+			return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ .Name | quote}})
+		}
+	{{- else if .IsMethod -}}
+		{{- if .NoErr -}}
+			return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }}), nil
+		{{- else -}}
+			return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }})
+		{{- end -}}
+	{{- else if .IsVariable -}}
+		return {{.GoReceiverName}}.{{.GoFieldName}}, nil
+	{{- end }}
+{{- end }}

vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl 🔗

@@ -117,7 +117,13 @@ func (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinitio
 		ec := executionContext{graphql.GetRequestContext(ctx), e}
 
 		buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
+		{{ if .Directives.LocationDirectives "QUERY" -}}
+			data := ec._queryMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
+				return ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet), nil
+			})
+		{{- else -}}
 			data := ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet)
+		{{- end }}
 			var buf bytes.Buffer
 			data.MarshalGQL(&buf)
 			return buf.Bytes()
@@ -138,7 +144,13 @@ func (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefini
 		ec := executionContext{graphql.GetRequestContext(ctx), e}
 
 		buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
+		{{ if .Directives.LocationDirectives "MUTATION" -}}
+			data := ec._mutationMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
+				return ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet), nil
+			})
+		{{- else -}}
 			data := ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet)
+		{{- end }}
 			var buf bytes.Buffer
 			data.MarshalGQL(&buf)
 			return buf.Bytes()
@@ -158,7 +170,13 @@ func (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDe
 	{{- if .SubscriptionRoot }}
 		ec := executionContext{graphql.GetRequestContext(ctx), e}
 
-		next := ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet)
+		{{ if .Directives.LocationDirectives "SUBSCRIPTION" -}}
+			next := ec._subscriptionMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
+				return ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet),nil
+			})
+		{{- else -}}
+			next := ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet)
+		{{- end }}
 		if ec.Errors != nil {
 			return graphql.OneShot(&graphql.Response{Data: []byte("null"), Errors: ec.Errors})
 		}
@@ -196,45 +214,6 @@ type executionContext struct {
 	*executableSchema
 }
 
-func (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) {
-	defer func() {
-		if r := recover(); r != nil {
-			ec.Error(ctx, ec.Recover(ctx, r))
-			ret = nil
-		}
-	}()
-	{{- if .Directives }}
-	rctx := graphql.GetResolverContext(ctx)
-	for _, d := range rctx.Field.Definition.Directives {
-		switch d.Name {
-		{{- range $directive := .Directives }}
-		case "{{$directive.Name}}":
-			if ec.directives.{{$directive.Name|ucFirst}} != nil {
-				{{- if $directive.Args }}
-					rawArgs := d.ArgumentMap(ec.Variables)
-					args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
-					if err != nil {
-						ec.Error(ctx, err)
-						return nil
-					}
-				{{- end }}
-				n := next
-				next = func(ctx context.Context) (interface{}, error) {
-					return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
-				}
-			}
-		{{- end }}
-		}
-	}
-	{{- end }}
-	res, err := ec.ResolverMiddleware(ctx, next)
-	if err != nil {
-		ec.Error(ctx, err)
-		return nil
-	}
-	return res
-}
-
 func (ec *executionContext) introspectSchema() (*introspection.Schema, error) {
 	if ec.DisableIntrospection {
 		return nil, errors.New("introspection disabled")

vendor/github.com/99designs/gqlgen/codegen/input.gotpl 🔗

@@ -1,8 +1,8 @@
 {{- range $input := .Inputs }}
 	{{- if not .HasUnmarshal }}
-	func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, v interface{}) ({{.Type | ref}}, error) {
+	func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{.Type | ref}}, error) {
 		var it {{.Type | ref}}
-		var asMap = v.(map[string]interface{})
+		var asMap = obj.(map[string]interface{})
 		{{ range $field := .Fields}}
 			{{- if $field.Default}}
 				if _, present := asMap[{{$field.Name|quote}}] ; !present {
@@ -16,22 +16,10 @@
 			{{- range $field := .Fields }}
 			case {{$field.Name|quote}}:
 				var err error
-				{{- if $field.Directives }}
-					getField0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
-
-					{{- range $i, $directive := $field.Directives }}
-						getField{{add $i 1}} := func(ctx context.Context) (res interface{}, err error) {
-							{{- range $dArg := $directive.Args }}
-								{{- if and $dArg.TypeReference.IsPtr ( notNil "Value" $dArg ) }}
-									{{ $dArg.VarName }} := {{ $dArg.Value | dump }}
-								{{- end }}
-							{{- end }}
-							n := getField{{$i}}
-							return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs "it" "n" }})
-						}
-					{{- end }}
-
-					tmp, err := getField{{$field.Directives|len}}(ctx)
+				{{- if $field.ImplDirectives }}
+					directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
+					{{ template "implDirectives" $field }}
+					tmp, err := directive{{$field.ImplDirectives|len}}(ctx)
 					if err != nil {
 						return it, err
 					}

vendor/github.com/99designs/gqlgen/handler/graphql.go 🔗

@@ -2,6 +2,8 @@ package handler
 
 import (
 	"context"
+	"crypto/sha256"
+	"encoding/hex"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -28,8 +30,30 @@ type params struct {
 	Query         string                 `json:"query"`
 	OperationName string                 `json:"operationName"`
 	Variables     map[string]interface{} `json:"variables"`
+	Extensions    *extensions            `json:"extensions"`
 }
 
+type extensions struct {
+	PersistedQuery *persistedQuery `json:"persistedQuery"`
+}
+
+type persistedQuery struct {
+	Sha256  string `json:"sha256Hash"`
+	Version int64  `json:"version"`
+}
+
+const (
+	errPersistedQueryNotSupported = "PersistedQueryNotSupported"
+	errPersistedQueryNotFound     = "PersistedQueryNotFound"
+)
+
+type PersistedQueryCache interface {
+	Add(ctx context.Context, hash string, query string)
+	Get(ctx context.Context, hash string) (string, bool)
+}
+
+type websocketInitFunc func(ctx context.Context, initPayload InitPayload) error
+
 type Config struct {
 	cacheSize                       int
 	upgrader                        websocket.Upgrader
@@ -40,10 +64,12 @@ type Config struct {
 	tracer                          graphql.Tracer
 	complexityLimit                 int
 	complexityLimitFunc             graphql.ComplexityLimitFunc
+	websocketInitFunc               websocketInitFunc
 	disableIntrospection            bool
 	connectionKeepAlivePingInterval time.Duration
 	uploadMaxMemory                 int64
 	uploadMaxSize                   int64
+	apqCache                        PersistedQueryCache
 }
 
 func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext {
@@ -250,6 +276,14 @@ func (tw *tracerWrapper) EndOperationExecution(ctx context.Context) {
 	tw.tracer1.EndOperationExecution(ctx)
 }
 
+// WebsocketInitFunc is called when the server receives connection init message from the client.
+// This can be used to check initial payload to see whether to accept the websocket connection.
+func WebsocketInitFunc(websocketInitFunc func(ctx context.Context, initPayload InitPayload) error) Option {
+	return func(cfg *Config) {
+		cfg.websocketInitFunc = websocketInitFunc
+	}
+}
+
 // CacheSize sets the maximum size of the query cache.
 // If size is less than or equal to 0, the cache is disabled.
 func CacheSize(size int) Option {
@@ -285,6 +319,13 @@ func WebsocketKeepAliveDuration(duration time.Duration) Option {
 	}
 }
 
+// Add cache that will hold queries for automatic persisted queries (APQ)
+func EnablePersistedQueryCache(cache PersistedQueryCache) Option {
+	return func(cfg *Config) {
+		cfg.apqCache = cache
+	}
+}
+
 const DefaultCacheSize = 1000
 const DefaultConnectionKeepAlivePingInterval = 25 * time.Second
 
@@ -344,6 +385,11 @@ type graphqlHandler struct {
 	exec  graphql.ExecutableSchema
 }
 
+func computeQueryHash(query string) string {
+	b := sha256.Sum256([]byte(query))
+	return hex.EncodeToString(b[:])
+}
+
 func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if r.Method == http.MethodOptions {
 		w.Header().Set("Allow", "OPTIONS, GET, POST")
@@ -369,6 +415,13 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 				return
 			}
 		}
+
+		if extensions := r.URL.Query().Get("extensions"); extensions != "" {
+			if err := jsonDecode(strings.NewReader(extensions), &reqParams.Extensions); err != nil {
+				sendErrorf(w, http.StatusBadRequest, "extensions could not be decoded")
+				return
+			}
+		}
 	case http.MethodPost:
 		mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
 		if err != nil {
@@ -409,6 +462,41 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	ctx := r.Context()
 
+	var queryHash string
+	apqRegister := false
+	apq := reqParams.Extensions != nil && reqParams.Extensions.PersistedQuery != nil
+	if apq {
+		// client has enabled apq
+		queryHash = reqParams.Extensions.PersistedQuery.Sha256
+		if gh.cfg.apqCache == nil {
+			// server has disabled apq
+			sendErrorf(w, http.StatusOK, errPersistedQueryNotSupported)
+			return
+		}
+		if reqParams.Extensions.PersistedQuery.Version != 1 {
+			sendErrorf(w, http.StatusOK, "Unsupported persisted query version")
+			return
+		}
+		if reqParams.Query == "" {
+			// client sent optimistic query hash without query string
+			query, ok := gh.cfg.apqCache.Get(ctx, queryHash)
+			if !ok {
+				sendErrorf(w, http.StatusOK, errPersistedQueryNotFound)
+				return
+			}
+			reqParams.Query = query
+		} else {
+			if computeQueryHash(reqParams.Query) != queryHash {
+				sendErrorf(w, http.StatusOK, "provided sha does not match query")
+				return
+			}
+			apqRegister = true
+		}
+	} else if reqParams.Query == "" {
+		sendErrorf(w, http.StatusUnprocessableEntity, "Must provide query string")
+		return
+	}
+
 	var doc *ast.QueryDocument
 	var cacheHit bool
 	if gh.cache != nil {
@@ -463,6 +551,11 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if apqRegister && gh.cfg.apqCache != nil {
+		// Add to persisted query cache
+		gh.cfg.apqCache.Add(ctx, queryHash, reqParams.Query)
+	}
+
 	switch op.Operation {
 	case ast.Query:
 		b, err := json.Marshal(gh.exec.Query(ctx, op))

vendor/github.com/99designs/gqlgen/handler/websocket.go 🔗

@@ -12,7 +12,7 @@ import (
 
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/gorilla/websocket"
-	"github.com/hashicorp/golang-lru"
+	lru "github.com/hashicorp/golang-lru"
 	"github.com/vektah/gqlparser"
 	"github.com/vektah/gqlparser/ast"
 	"github.com/vektah/gqlparser/gqlerror"
@@ -94,6 +94,14 @@ func (c *wsConnection) init() bool {
 			}
 		}
 
+		if c.cfg.websocketInitFunc != nil {
+			if err := c.cfg.websocketInitFunc(c.ctx, c.initPayload); err != nil {
+				c.sendConnectionError(err.Error())
+				c.close(websocket.CloseNormalClosure, "terminated")
+				return false
+			}
+		}
+
 		c.write(&operationMessage{Type: connectionAckMsg})
 	case connectionTerminateMsg:
 		c.close(websocket.CloseNormalClosure, "terminated")

vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl 🔗

@@ -20,18 +20,18 @@ type {{.ResolverType}} struct {}
 {{ range $object := .Objects -}}
 	{{- if $object.HasResolvers -}}
 		func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} {
-			return &{{lcFirst $object.Name}}Resolver{r}
+			return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r}
 		}
 	{{ end -}}
 {{ end }}
 
 {{ range $object := .Objects -}}
 	{{- if $object.HasResolvers -}}
-		type {{lcFirst $object.Name}}Resolver struct { *Resolver }
+		type {{lcFirst $object.Name}}{{ucFirst $.ResolverType}} struct { *{{$.ResolverType}} }
 
 		{{ range $field := $object.Fields -}}
 			{{- if $field.IsResolver -}}
-			func (r *{{lcFirst $object.Name}}Resolver) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} {
+			func (r *{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} {
 				panic("not implemented")
 			}
 			{{ end -}}

vendor/github.com/99designs/gqlgen/plugin/schemaconfig/schemaconfig.go 🔗

@@ -0,0 +1,93 @@
+package schemaconfig
+
+import (
+	"github.com/99designs/gqlgen/codegen/config"
+	"github.com/99designs/gqlgen/plugin"
+	"github.com/vektah/gqlparser/ast"
+)
+
+func New() plugin.Plugin {
+	return &Plugin{}
+}
+
+type Plugin struct{}
+
+var _ plugin.ConfigMutator = &Plugin{}
+
+func (m *Plugin) Name() string {
+	return "schemaconfig"
+}
+
+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
+	}
+
+	cfg.Directives["goModel"] = config.DirectiveConfig{
+		SkipRuntime: true,
+	}
+
+	cfg.Directives["goField"] = config.DirectiveConfig{
+		SkipRuntime: true,
+	}
+
+	for _, schemaType := range schema.Types {
+		if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription {
+			continue
+		}
+
+		if bd := schemaType.Directives.ForName("goModel"); bd != nil {
+			if ma := bd.Arguments.ForName("model"); ma != nil {
+				if mv, err := ma.Value.Value(nil); err == nil {
+					cfg.Models.Add(schemaType.Name, mv.(string))
+				}
+			}
+			if ma := bd.Arguments.ForName("models"); ma != nil {
+				if mvs, err := ma.Value.Value(nil); err == nil {
+					for _, mv := range mvs.([]interface{}) {
+						cfg.Models.Add(schemaType.Name, mv.(string))
+					}
+				}
+			}
+		}
+
+		if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject {
+			for _, field := range schemaType.Fields {
+				if fd := field.Directives.ForName("goField"); fd != nil {
+					forceResolver := cfg.Models[schemaType.Name].Fields[field.Name].Resolver
+					fieldName := cfg.Models[schemaType.Name].Fields[field.Name].FieldName
+
+					if ra := fd.Arguments.ForName("forceResolver"); ra != nil {
+						if fr, err := ra.Value.Value(nil); err == nil {
+							forceResolver = fr.(bool)
+						}
+					}
+
+					if na := fd.Arguments.ForName("name"); na != nil {
+						if fr, err := na.Value.Value(nil); err == nil {
+							fieldName = fr.(string)
+						}
+					}
+
+					if cfg.Models[schemaType.Name].Fields == nil {
+						cfg.Models[schemaType.Name] = config.TypeMapEntry{
+							Model:  cfg.Models[schemaType.Name].Model,
+							Fields: map[string]config.TypeMapField{},
+						}
+					}
+
+					cfg.Models[schemaType.Name].Fields[field.Name] = config.TypeMapField{
+						FieldName: fieldName,
+						Resolver:  forceResolver,
+					}
+				}
+			}
+		}
+	}
+	return nil
+}