Update gqlgen vendors

This commit is contained in:
Amine Hilaly 2019-07-07 13:37:03 +02:00
parent cb7fefdc99
commit e381d5554a
No known key found for this signature in database
GPG Key ID: 3F4C54B792F211C1
19 changed files with 632 additions and 143 deletions

View File

@ -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(),
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"}},

View File

@ -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)

View File

@ -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"
}

View File

@ -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 }}

View File

@ -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 {

View File

@ -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 }}

View File

@ -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")

View File

@ -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
}

View File

@ -1,3 +1,3 @@
package graphql
const Version = "v0.9.0"
const Version = "v0.9.1"

View File

@ -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))

View File

@ -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")

View File

@ -72,6 +72,11 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
return err
}
err = cfg.Autobind(schema)
if err != nil {
return err
}
cfg.InjectBuiltins(schema)
binder, err := cfg.NewBinder(schema)

View File

@ -19,6 +19,7 @@ type Plugin struct{}
var _ plugin.CodeGenerator = &Plugin{}
func (m *Plugin) Name() string {
// TODO: typo, should be resolvergen
return "resovlergen"
}
func (m *Plugin) GenerateCode(data *codegen.Data) error {

View File

@ -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 -}}

View File

@ -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
}