diff options
author | Michael Muré <batolettre@gmail.com> | 2019-05-15 18:20:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-15 18:20:54 +0200 |
commit | 22f435bd2a8fc35bde96eafb50b3f78650a5983b (patch) | |
tree | 629b72f3d95ac7b7806fb83096a3b89e14f745ef /vendor/github.com | |
parent | 97476ff5fadaf0a457d0f0133db58415b6075940 (diff) | |
parent | 8bab279114f06f10e22435b0caf9002201831555 (diff) | |
download | git-bug-22f435bd2a8fc35bde96eafb50b3f78650a5983b.tar.gz |
Merge pull request #134 from A-Hilaly/gqlgen
Upgrade gqlgen version to v0.9.0
Diffstat (limited to 'vendor/github.com')
19 files changed, 551 insertions, 117 deletions
diff --git a/vendor/github.com/99designs/gqlgen/codegen/complexity.go b/vendor/github.com/99designs/gqlgen/codegen/complexity.go index 66d21a84..e9c6a20e 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/complexity.go +++ b/vendor/github.com/99designs/gqlgen/codegen/complexity.go @@ -1,10 +1,10 @@ package codegen -func (o *Object) UniqueFields() map[string]*Field { - m := map[string]*Field{} +func (o *Object) UniqueFields() map[string][]*Field { + m := map[string][]*Field{} for _, f := range o.Fields { - m[f.GoFieldName] = f + m[f.GoFieldName] = append(m[f.GoFieldName], f) } return m diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/binder.go b/vendor/github.com/99designs/gqlgen/codegen/config/binder.go index f3956387..cea904ad 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/binder.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/binder.go @@ -238,25 +238,6 @@ func (t *TypeReference) IsScalar() bool { return t.Definition.Kind == ast.Scalar } -func (t *TypeReference) HasIsZero() bool { - it := t.GO - if ptr, isPtr := it.(*types.Pointer); isPtr { - it = ptr.Elem() - } - namedType, ok := it.(*types.Named) - if !ok { - return false - } - - for i := 0; i < namedType.NumMethods(); i++ { - switch namedType.Method(i).Name() { - case "IsZero": - return true - } - } - return false -} - func (t *TypeReference) UniquenessKey() string { var nullability = "O" if t.GQL.NonNull { @@ -368,7 +349,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret } else if hasMethod(obj.Type(), "MarshalGQL") && hasMethod(obj.Type(), "UnmarshalGQL") { ref.GO = obj.Type() ref.IsMarshaler = true - } else if underlying := basicUnderlying(obj.Type()); underlying != nil && underlying.Kind() == types.String { + } else if underlying := basicUnderlying(obj.Type()); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String { // Special case for named types wrapping strings. Used by default enum implementations. ref.GO = obj.Type() @@ -402,7 +383,11 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type { if t.Elem != nil { - return types.NewSlice(b.CopyModifiersFromAst(t.Elem, base)) + child := b.CopyModifiersFromAst(t.Elem, base) + if _, isStruct := child.Underlying().(*types.Struct); isStruct { + child = types.NewPointer(child) + } + return types.NewSlice(child) } var isInterface bool diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/config.go b/vendor/github.com/99designs/gqlgen/codegen/config/config.go index 0c72420e..1725adab 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/config.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/config.go @@ -136,7 +136,7 @@ func (c *PackageConfig) normalize() error { // If Package is not set, first attempt to load the package at the output dir. If that fails // fallback to just the base dir name of the output filename. if c.Package == "" { - c.Package = code.NameForPackage(c.ImportPath()) + c.Package = code.NameForDir(c.Dir()) } return nil @@ -363,8 +363,10 @@ func (c *Config) InjectBuiltins(s *ast.Schema) { // These are additional types that are injected if defined in the schema as scalars. extraBuiltins := TypeMap{ - "Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}}, - "Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}}, + "Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}}, + "Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}}, + "Upload": {Model: StringList{"github.com/99designs/gqlgen/graphql.Upload"}}, + "Any": {Model: StringList{"github.com/99designs/gqlgen/graphql.Any"}}, } for typeName, entry := range extraBuiltins { diff --git a/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl b/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl index dce8ce97..5753f1d1 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl @@ -4,6 +4,7 @@ {{ reserveImport "strconv" }} {{ reserveImport "time" }} {{ reserveImport "sync" }} +{{ reserveImport "sync/atomic" }} {{ reserveImport "errors" }} {{ reserveImport "bytes" }} @@ -46,7 +47,8 @@ type ComplexityRoot struct { {{ range $object := .Objects }} {{ if not $object.IsReserved -}} {{ $object.Name|go }} struct { - {{ range $field := $object.UniqueFields -}} + {{ range $_, $fields := $object.UniqueFields }} + {{- $field := index $fields 0 -}} {{ if not $field.IsReserved -}} {{ $field.GoFieldName }} {{ $field.ComplexitySignature }} {{ end }} @@ -84,20 +86,25 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in switch typeName + "." + field { {{ range $object := .Objects }} {{ if not $object.IsReserved }} - {{ range $field := $object.UniqueFields }} - {{ if not $field.IsReserved }} - case "{{$object.Name}}.{{$field.GoFieldName}}": - if e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}} == nil { - break - } - {{ if $field.Args }} - args, err := ec.{{ $field.ArgsFunc }}(context.TODO(),rawArgs) - if err != nil { - return 0, false + {{ range $_, $fields := $object.UniqueFields }} + {{- $len := len $fields }} + {{- range $i, $field := $fields }} + {{- $last := eq (add $i 1) $len }} + {{- if not $field.IsReserved }} + {{- if eq $i 0 }}case {{ end }}"{{$object.Name}}.{{$field.Name}}"{{ if not $last }},{{ else }}: + if e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}} == nil { + break } + {{ if $field.Args }} + args, err := ec.{{ $field.ArgsFunc }}(context.TODO(),rawArgs) + if err != nil { + return 0, false + } + {{ end }} + return e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{ end }}), true {{ end }} - return e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{end}}), true - {{ end }} + {{- end }} + {{- end }} {{ end }} {{ end }} {{ end }} diff --git a/vendor/github.com/99designs/gqlgen/codegen/object.gotpl b/vendor/github.com/99designs/gqlgen/codegen/object.gotpl index 19da1b19..98a75740 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/object.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/object.gotpl @@ -4,7 +4,7 @@ var {{ $object.Name|lcFirst}}Implementors = {{$object.Implementors}} {{- if .Stream }} func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet) func() graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, {{$object.Name|lcFirst}}Implementors) + fields := graphql.CollectFields(ec.RequestContext, sel, {{$object.Name|lcFirst}}Implementors) ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ Object: {{$object.Name|quote}}, }) @@ -24,7 +24,7 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec } {{- else }} func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet{{ if not $object.Root }},obj {{$object.Reference | ref }}{{ end }}) graphql.Marshaler { - fields := graphql.CollectFields(ctx, sel, {{$object.Name|lcFirst}}Implementors) + fields := graphql.CollectFields(ec.RequestContext, sel, {{$object.Name|lcFirst}}Implementors) {{if $object.Root}} ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ Object: {{$object.Name|quote}}, @@ -32,7 +32,7 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec {{end}} out := graphql.NewFieldSet(fields) - invalid := false + var invalids uint32 for i, field := range fields { switch field.Name { case "__typename": @@ -50,7 +50,11 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec res = ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}}) {{- if $field.TypeReference.GQL.NonNull }} if res == graphql.Null { - invalid = true + {{- if $object.IsConcurrent }} + atomic.AddUint32(&invalids, 1) + {{- else }} + invalids++ + {{- end }} } {{- end }} return res @@ -59,7 +63,11 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec out.Values[i] = ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}}) {{- if $field.TypeReference.GQL.NonNull }} if out.Values[i] == graphql.Null { - invalid = true + {{- if $object.IsConcurrent }} + atomic.AddUint32(&invalids, 1) + {{- else }} + invalids++ + {{- end }} } {{- end }} {{- end }} @@ -69,7 +77,7 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec } } out.Dispatch() - if invalid { return graphql.Null } + if invalids > 0 { return graphql.Null } return out } {{- end }} diff --git a/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go b/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go index 4c292732..f2fcb568 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go +++ b/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go @@ -19,18 +19,36 @@ import ( "github.com/pkg/errors" ) +// CurrentImports keeps track of all the import declarations that are needed during the execution of a plugin. // this is done with a global because subtemplates currently get called in functions. Lets aim to remove this eventually. var CurrentImports *Imports +// Options specify various parameters to rendering a template. type Options struct { - PackageName string + // PackageName is a helper that specifies the package header declaration. + // In other words, when you write the template you don't need to specify `package X` + // at the top of the file. By providing PackageName in the Options, the Render + // function will do that for you. + PackageName string + // Template is a string of the entire template that + // will be parsed and rendered. If it's empty, + // the plugin processor will look for .gotpl files + // in the same directory of where you wrote the plugin. + Template string + // Filename is the name of the file that will be + // written to the system disk once the template is rendered. Filename string RegionTags bool GeneratedHeader bool - Data interface{} - Funcs template.FuncMap + // Data will be passed to the template execution. + Data interface{} + Funcs template.FuncMap } +// Render renders a gql plugin template from the given Options. Render is an +// abstraction of the text/template package that makes it easier to write gqlgen +// plugins. If Options.Template is empty, the Render function will look for `.gotpl` +// files inside the directory where you wrote the plugin. func Render(cfg Options) error { if CurrentImports != nil { panic(fmt.Errorf("recursive or concurrent call to RenderToFile detected")) @@ -48,31 +66,40 @@ func Render(cfg Options) error { t := template.New("").Funcs(funcs) var roots []string - // load all the templates in the directory - err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { + if cfg.Template != "" { + var err error + t, err = t.New("template.gotpl").Parse(cfg.Template) if err != nil { - return err - } - name := filepath.ToSlash(strings.TrimPrefix(path, rootDir+string(os.PathSeparator))) - if !strings.HasSuffix(info.Name(), ".gotpl") { - return nil - } - b, err := ioutil.ReadFile(path) - if err != nil { - return err + return errors.Wrap(err, "error with provided template") } + roots = append(roots, "template.gotpl") + } else { + // load all the templates in the directory + err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + name := filepath.ToSlash(strings.TrimPrefix(path, rootDir+string(os.PathSeparator))) + if !strings.HasSuffix(info.Name(), ".gotpl") { + return nil + } + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } - t, err = t.New(name).Parse(string(b)) - if err != nil { - return errors.Wrap(err, cfg.Filename) - } + t, err = t.New(name).Parse(string(b)) + if err != nil { + return errors.Wrap(err, cfg.Filename) + } - roots = append(roots, name) + roots = append(roots, name) - return nil - }) - if err != nil { - return errors.Wrap(err, "locating templates") + return nil + }) + if err != nil { + return errors.Wrap(err, "locating templates") + } } // then execute all the important looking ones in order, adding them to the same file @@ -91,7 +118,7 @@ func Render(cfg Options) error { if cfg.RegionTags { buf.WriteString("\n// region " + center(70, "*", " "+root+" ") + "\n") } - err = t.Lookup(root).Execute(&buf, cfg.Data) + err := t.Lookup(root).Execute(&buf, cfg.Data) if err != nil { return errors.Wrap(err, root) } @@ -110,7 +137,7 @@ func Render(cfg Options) error { result.WriteString("import (\n") result.WriteString(CurrentImports.String()) result.WriteString(")\n") - _, err = buf.WriteTo(&result) + _, err := buf.WriteTo(&result) if err != nil { return err } diff --git a/vendor/github.com/99designs/gqlgen/codegen/type.gotpl b/vendor/github.com/99designs/gqlgen/codegen/type.gotpl index f727baac..cb2782c3 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/type.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/type.gotpl @@ -56,15 +56,6 @@ {{- end }} return graphql.Null } - {{- else if $type.HasIsZero }} - if v.IsZero() { - {{- if $type.GQL.NonNull }} - if !ec.HasError(graphql.GetResolverContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - {{- end }} - return graphql.Null - } {{- end }} {{- if $type.IsSlice }} @@ -119,6 +110,14 @@ {{- else if $type.Marshaler }} {{- if $type.IsPtr }} return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v) + {{- else if $type.GQL.NonNull }} + res := {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}(v){{else}}v{{- end }}) + if res == graphql.Null { + if !ec.HasError(graphql.GetResolverContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res {{- else }} return {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}(v){{else}}v{{- end }}) {{- end }} diff --git a/vendor/github.com/99designs/gqlgen/graphql/any.go b/vendor/github.com/99designs/gqlgen/graphql/any.go new file mode 100644 index 00000000..6ea8bf2e --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/any.go @@ -0,0 +1,19 @@ +package graphql + +import ( + "encoding/json" + "io" +) + +func MarshalAny(v interface{}) Marshaler { + return WriterFunc(func(w io.Writer) { + err := json.NewEncoder(w).Encode(v) + if err != nil { + panic(err) + } + }) +} + +func UnmarshalAny(v interface{}) (interface{}, error) { + return v, nil +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/context.go b/vendor/github.com/99designs/gqlgen/graphql/context.go index 58d3c741..356f5175 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/context.go +++ b/vendor/github.com/99designs/gqlgen/graphql/context.go @@ -132,14 +132,14 @@ func WithResolverContext(ctx context.Context, rc *ResolverContext) context.Conte // This is just a convenient wrapper method for CollectFields func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField { resctx := GetResolverContext(ctx) - return CollectFields(ctx, resctx.Field.Selections, satisfies) + return CollectFields(GetRequestContext(ctx), resctx.Field.Selections, satisfies) } // CollectAllFields returns a slice of all GraphQL field names that were selected for the current resolver context. // The slice will contain the unique set of all field names requested regardless of fragment type conditions. func CollectAllFields(ctx context.Context) []string { resctx := GetResolverContext(ctx) - collected := CollectFields(ctx, resctx.Field.Selections, nil) + collected := CollectFields(GetRequestContext(ctx), resctx.Field.Selections, nil) uniq := make([]string, 0, len(collected)) Next: for _, f := range collected { diff --git a/vendor/github.com/99designs/gqlgen/graphql/exec.go b/vendor/github.com/99designs/gqlgen/graphql/exec.go index 17c57bf6..3e00a4d5 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/exec.go +++ b/vendor/github.com/99designs/gqlgen/graphql/exec.go @@ -19,12 +19,12 @@ type ExecutableSchema interface { // CollectFields returns the set of fields from an ast.SelectionSet where all collected fields satisfy at least one of the GraphQL types // passed through satisfies. Providing an empty or nil slice for satisfies will return collect all fields regardless of fragment // type conditions. -func CollectFields(ctx context.Context, selSet ast.SelectionSet, satisfies []string) []CollectedField { - return collectFields(GetRequestContext(ctx), selSet, satisfies, map[string]bool{}) +func CollectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies []string) []CollectedField { + return collectFields(reqCtx, selSet, satisfies, map[string]bool{}) } func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies []string, visited map[string]bool) []CollectedField { - var groupedFields []CollectedField + groupedFields := make([]CollectedField, 0, len(selSet)) for _, sel := range selSet { switch sel := sel.(type) { @@ -32,7 +32,7 @@ func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies [] if !shouldIncludeNode(sel.Directives, reqCtx.Variables) { continue } - f := getOrCreateField(&groupedFields, sel.Alias, func() CollectedField { + f := getOrCreateAndAppendField(&groupedFields, sel.Alias, func() CollectedField { return CollectedField{Field: sel} }) @@ -45,7 +45,7 @@ func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies [] continue } for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) { - f := getOrCreateField(&groupedFields, childField.Name, func() CollectedField { return childField }) + f := getOrCreateAndAppendField(&groupedFields, childField.Name, func() CollectedField { return childField }) f.Selections = append(f.Selections, childField.Selections...) } @@ -70,10 +70,9 @@ func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies [] } for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) { - f := getOrCreateField(&groupedFields, childField.Name, func() CollectedField { return childField }) + f := getOrCreateAndAppendField(&groupedFields, childField.Name, func() CollectedField { return childField }) f.Selections = append(f.Selections, childField.Selections...) } - default: panic(fmt.Errorf("unsupported %T", sel)) } @@ -97,7 +96,7 @@ func instanceOf(val string, satisfies []string) bool { return false } -func getOrCreateField(c *[]CollectedField, name string, creator func() CollectedField) *CollectedField { +func getOrCreateAndAppendField(c *[]CollectedField, name string, creator func() CollectedField) *CollectedField { for i, cf := range *c { if cf.Alias == name { return &(*c)[i] @@ -111,6 +110,10 @@ func getOrCreateField(c *[]CollectedField, name string, creator func() Collected } func shouldIncludeNode(directives ast.DirectiveList, variables map[string]interface{}) bool { + if len(directives) == 0 { + return true + } + skip, include := false, true if d := directives.ForName("skip"); d != nil { diff --git a/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go b/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go index f1228edf..9aceebdc 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go +++ b/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go @@ -70,6 +70,10 @@ func (t *Type) Fields(includeDeprecated bool) []Field { continue } + if !includeDeprecated && f.Directives.ForName("deprecated") != nil { + continue + } + var args []InputValue for _, arg := range f.Arguments { args = append(args, InputValue{ diff --git a/vendor/github.com/99designs/gqlgen/graphql/time.go b/vendor/github.com/99designs/gqlgen/graphql/time.go index 4f448560..9945f3fb 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/time.go +++ b/vendor/github.com/99designs/gqlgen/graphql/time.go @@ -8,6 +8,10 @@ import ( ) func MarshalTime(t time.Time) Marshaler { + if t.IsZero() { + return Null + } + return WriterFunc(func(w io.Writer) { io.WriteString(w, strconv.Quote(t.Format(time.RFC3339))) }) diff --git a/vendor/github.com/99designs/gqlgen/graphql/upload.go b/vendor/github.com/99designs/gqlgen/graphql/upload.go new file mode 100644 index 00000000..22d61031 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/upload.go @@ -0,0 +1,26 @@ +package graphql + +import ( + "fmt" + "io" +) + +type Upload struct { + File io.Reader + Filename string + Size int64 +} + +func MarshalUpload(f Upload) Marshaler { + return WriterFunc(func(w io.Writer) { + io.Copy(w, f.File) + }) +} + +func UnmarshalUpload(v interface{}) (Upload, error) { + upload, ok := v.(Upload) + if !ok { + return Upload{}, fmt.Errorf("%T is not an Upload", v) + } + return upload, nil +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/version.go b/vendor/github.com/99designs/gqlgen/graphql/version.go index 88014abf..11dc6b01 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/version.go +++ b/vendor/github.com/99designs/gqlgen/graphql/version.go @@ -1,3 +1,3 @@ package graphql -const Version = "v0.8.3" +const Version = "v0.9.0" diff --git a/vendor/github.com/99designs/gqlgen/handler/graphql.go b/vendor/github.com/99designs/gqlgen/handler/graphql.go index 92a0471c..a2254222 100644 --- a/vendor/github.com/99designs/gqlgen/handler/graphql.go +++ b/vendor/github.com/99designs/gqlgen/handler/graphql.go @@ -3,16 +3,21 @@ package handler import ( "context" "encoding/json" + "errors" "fmt" "io" + "io/ioutil" + "mime" "net/http" + "os" + "strconv" "strings" "time" "github.com/99designs/gqlgen/complexity" "github.com/99designs/gqlgen/graphql" "github.com/gorilla/websocket" - "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru" "github.com/vektah/gqlparser/ast" "github.com/vektah/gqlparser/gqlerror" "github.com/vektah/gqlparser/parser" @@ -37,6 +42,8 @@ type Config struct { complexityLimitFunc graphql.ComplexityLimitFunc disableIntrospection bool connectionKeepAlivePingInterval time.Duration + uploadMaxMemory int64 + uploadMaxSize int64 } func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext { @@ -251,6 +258,23 @@ func CacheSize(size int) Option { } } +// UploadMaxSize sets the maximum number of bytes used to parse a request body +// as multipart/form-data. +func UploadMaxSize(size int64) Option { + return func(cfg *Config) { + cfg.uploadMaxSize = size + } +} + +// UploadMaxMemory sets the maximum number of bytes used to parse a request body +// as multipart/form-data in memory, with the remainder stored on disk in +// temporary files. +func UploadMaxMemory(size int64) Option { + return func(cfg *Config) { + cfg.uploadMaxMemory = size + } +} + // WebsocketKeepAliveDuration allows you to reconfigure the keepalive behavior. // By default, keepalive is enabled with a DefaultConnectionKeepAlivePingInterval // duration. Set handler.connectionKeepAlivePingInterval = 0 to disable keepalive @@ -264,9 +288,20 @@ func WebsocketKeepAliveDuration(duration time.Duration) Option { const DefaultCacheSize = 1000 const DefaultConnectionKeepAlivePingInterval = 25 * time.Second +// DefaultUploadMaxMemory is the maximum number of bytes used to parse a request body +// as multipart/form-data in memory, with the remainder stored on disk in +// temporary files. +const DefaultUploadMaxMemory = 32 << 20 + +// DefaultUploadMaxSize is maximum number of bytes used to parse a request body +// as multipart/form-data. +const DefaultUploadMaxSize = 32 << 20 + func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc { cfg := &Config{ cacheSize: DefaultCacheSize, + uploadMaxMemory: DefaultUploadMaxMemory, + uploadMaxSize: DefaultUploadMaxSize, connectionKeepAlivePingInterval: DefaultConnectionKeepAlivePingInterval, upgrader: websocket.Upgrader{ ReadBufferSize: 1024, @@ -335,8 +370,36 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } case http.MethodPost: - if err := jsonDecode(r.Body, &reqParams); err != nil { - sendErrorf(w, http.StatusBadRequest, "json body could not be decoded: "+err.Error()) + mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + sendErrorf(w, http.StatusBadRequest, "error parsing request Content-Type") + return + } + + switch mediaType { + case "application/json": + if err := jsonDecode(r.Body, &reqParams); err != nil { + sendErrorf(w, http.StatusBadRequest, "json body could not be decoded: "+err.Error()) + return + } + + case "multipart/form-data": + var closers []io.Closer + var tmpFiles []string + defer func() { + for i := len(closers) - 1; 0 <= i; i-- { + _ = closers[i].Close() + } + for _, tmpFile := range tmpFiles { + _ = os.Remove(tmpFile) + } + }() + if err := processMultipart(w, r, &reqParams, &closers, &tmpFiles, gh.cfg.uploadMaxSize, gh.cfg.uploadMaxMemory); err != nil { + sendErrorf(w, http.StatusBadRequest, "multipart body could not be decoded: "+err.Error()) + return + } + default: + sendErrorf(w, http.StatusBadRequest, "unsupported Content-Type: "+mediaType) return } default: @@ -493,3 +556,154 @@ func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) { func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) { sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)}) } + +type bytesReader struct { + s *[]byte + i int64 // current reading index + prevRune int // index of previous rune; or < 0 +} + +func (r *bytesReader) Read(b []byte) (n int, err error) { + if r.s == nil { + return 0, errors.New("byte slice pointer is nil") + } + if r.i >= int64(len(*r.s)) { + return 0, io.EOF + } + r.prevRune = -1 + n = copy(b, (*r.s)[r.i:]) + r.i += int64(n) + return +} + +func processMultipart(w http.ResponseWriter, r *http.Request, request *params, closers *[]io.Closer, tmpFiles *[]string, uploadMaxSize, uploadMaxMemory int64) error { + var err error + if r.ContentLength > uploadMaxSize { + return errors.New("failed to parse multipart form, request body too large") + } + r.Body = http.MaxBytesReader(w, r.Body, uploadMaxSize) + if err = r.ParseMultipartForm(uploadMaxMemory); err != nil { + if strings.Contains(err.Error(), "request body too large") { + return errors.New("failed to parse multipart form, request body too large") + } + return errors.New("failed to parse multipart form") + } + *closers = append(*closers, r.Body) + + if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), &request); err != nil { + return errors.New("operations form field could not be decoded") + } + + var uploadsMap = map[string][]string{} + if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil { + return errors.New("map form field could not be decoded") + } + + var upload graphql.Upload + for key, paths := range uploadsMap { + if len(paths) == 0 { + return fmt.Errorf("invalid empty operations paths list for key %s", key) + } + file, header, err := r.FormFile(key) + if err != nil { + return fmt.Errorf("failed to get key %s from form", key) + } + *closers = append(*closers, file) + + if len(paths) == 1 { + upload = graphql.Upload{ + File: file, + Size: header.Size, + Filename: header.Filename, + } + err = addUploadToOperations(request, upload, key, paths[0]) + if err != nil { + return err + } + } else { + if r.ContentLength < uploadMaxMemory { + fileBytes, err := ioutil.ReadAll(file) + if err != nil { + return fmt.Errorf("failed to read file for key %s", key) + } + for _, path := range paths { + upload = graphql.Upload{ + File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1}, + Size: header.Size, + Filename: header.Filename, + } + err = addUploadToOperations(request, upload, key, path) + if err != nil { + return err + } + } + } else { + tmpFile, err := ioutil.TempFile(os.TempDir(), "gqlgen-") + if err != nil { + return fmt.Errorf("failed to create temp file for key %s", key) + } + tmpName := tmpFile.Name() + *tmpFiles = append(*tmpFiles, tmpName) + _, err = io.Copy(tmpFile, file) + if err != nil { + if err := tmpFile.Close(); err != nil { + return fmt.Errorf("failed to copy to temp file and close temp file for key %s", key) + } + return fmt.Errorf("failed to copy to temp file for key %s", key) + } + if err := tmpFile.Close(); err != nil { + return fmt.Errorf("failed to close temp file for key %s", key) + } + for _, path := range paths { + pathTmpFile, err := os.Open(tmpName) + if err != nil { + return fmt.Errorf("failed to open temp file for key %s", key) + } + *closers = append(*closers, pathTmpFile) + upload = graphql.Upload{ + File: pathTmpFile, + Size: header.Size, + Filename: header.Filename, + } + err = addUploadToOperations(request, upload, key, path) + if err != nil { + return err + } + } + } + } + } + return nil +} + +func addUploadToOperations(request *params, upload graphql.Upload, key, path string) error { + if !strings.HasPrefix(path, "variables.") { + return fmt.Errorf("invalid operations paths for key %s", key) + } + + var ptr interface{} = request.Variables + parts := strings.Split(path, ".") + + // skip the first part (variables) because we started there + for i, p := range parts[1:] { + last := i == len(parts)-2 + if ptr == nil { + return fmt.Errorf("path is missing \"variables.\" prefix, key: %s, path: %s", key, path) + } + if index, parseNbrErr := strconv.Atoi(p); parseNbrErr == nil { + if last { + ptr.([]interface{})[index] = upload + } else { + ptr = ptr.([]interface{})[index] + } + } else { + if last { + ptr.(map[string]interface{})[p] = upload + } else { + ptr = ptr.(map[string]interface{})[p] + } + } + } + + return nil +} diff --git a/vendor/github.com/99designs/gqlgen/handler/mock.go b/vendor/github.com/99designs/gqlgen/handler/mock.go new file mode 100644 index 00000000..3e70cf03 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/handler/mock.go @@ -0,0 +1,57 @@ +package handler + +import ( + "context" + + "github.com/99designs/gqlgen/graphql" + "github.com/vektah/gqlparser" + "github.com/vektah/gqlparser/ast" +) + +type executableSchemaMock struct { + MutationFunc func(ctx context.Context, op *ast.OperationDefinition) *graphql.Response +} + +var _ graphql.ExecutableSchema = &executableSchemaMock{} + +func (e *executableSchemaMock) Schema() *ast.Schema { + return gqlparser.MustLoadSchema(&ast.Source{Input: ` + schema { query: Query, mutation: Mutation } + type Query { + empty: String! + } + scalar Upload + type File { + id: Int! + } + input UploadFile { + id: Int! + file: Upload! + } + type Mutation { + singleUpload(file: Upload!): File! + singleUploadWithPayload(req: UploadFile!): File! + multipleUpload(files: [Upload!]!): [File!]! + multipleUploadWithPayload(req: [UploadFile!]!): [File!]! + } + `}) +} + +func (e *executableSchemaMock) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) { + return 0, false +} + +func (e *executableSchemaMock) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { + return graphql.ErrorResponse(ctx, "queries are not supported") +} + +func (e *executableSchemaMock) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { + return e.MutationFunc(ctx, op) +} + +func (e *executableSchemaMock) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response { + return func() *graphql.Response { + <-ctx.Done() + return nil + } +} diff --git a/vendor/github.com/99designs/gqlgen/internal/code/imports.go b/vendor/github.com/99designs/gqlgen/internal/code/imports.go index 2384e87d..75c30fe1 100644 --- a/vendor/github.com/99designs/gqlgen/internal/code/imports.go +++ b/vendor/github.com/99designs/gqlgen/internal/code/imports.go @@ -2,44 +2,96 @@ package code import ( "errors" + "go/build" + "go/parser" + "go/token" + "io/ioutil" "path/filepath" + "regexp" + "strings" "sync" "golang.org/x/tools/go/packages" ) -var pathForDirCache = sync.Map{} +var nameForPackageCache = sync.Map{} -// ImportPathFromDir takes an *absolute* path and returns a golang import path for the package, and returns an error if it isn't on the gopath -func ImportPathForDir(dir string) string { - if v, ok := pathForDirCache.Load(dir); ok { - return v.(string) +var gopaths []string + +func init() { + gopaths = filepath.SplitList(build.Default.GOPATH) + for i, p := range gopaths { + gopaths[i] = filepath.ToSlash(filepath.Join(p, "src")) } +} - p, _ := packages.Load(&packages.Config{ - Dir: dir, - }, ".") +// NameForDir manually looks for package stanzas in files located in the given directory. This can be +// much faster than having to consult go list, because we already know exactly where to look. +func NameForDir(dir string) string { + dir, err := filepath.Abs(dir) + if err != nil { + return SanitizePackageName(filepath.Base(dir)) + } + files, err := ioutil.ReadDir(dir) + if err != nil { + return SanitizePackageName(filepath.Base(dir)) + } + fset := token.NewFileSet() + for _, file := range files { + if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") { + continue + } + + filename := filepath.Join(dir, file.Name()) + if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil { + return src.Name.Name + } + } - // If the dir dosent exist yet, keep walking up the directory tree trying to find a match - if len(p) != 1 { - parent, err := filepath.Abs(filepath.Join(dir, "..")) + return SanitizePackageName(filepath.Base(dir)) +} + +// ImportPathForDir takes a path and returns a golang import path for the package +func ImportPathForDir(dir string) (res string) { + dir, err := filepath.Abs(dir) + if err != nil { + panic(err) + } + dir = filepath.ToSlash(dir) + + modDir := dir + assumedPart := "" + for { + f, err := ioutil.ReadFile(filepath.Join(modDir, "/", "go.mod")) + if err == nil { + // found it, stop searching + return string(modregex.FindSubmatch(f)[1]) + assumedPart + } + + assumedPart = "/" + filepath.Base(modDir) + assumedPart + modDir, err = filepath.Abs(filepath.Join(modDir, "..")) if err != nil { panic(err) } + // Walked all the way to the root and didnt find anything :'( - if parent == dir { - return "" + if modDir == "/" { + break } - return ImportPathForDir(parent) + "/" + filepath.Base(dir) } - pathForDirCache.Store(dir, p[0].PkgPath) + for _, gopath := range gopaths { + if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) { + return dir[len(gopath)+1:] + } + } - return p[0].PkgPath + return "" } -var nameForPackageCache = sync.Map{} +var modregex = regexp.MustCompile("module (.*)\n") +// NameForPackage returns the package name for a given import path. This can be really slow. func NameForPackage(importPath string) string { if importPath == "" { panic(errors.New("import path can not be empty")) @@ -48,7 +100,9 @@ func NameForPackage(importPath string) string { return v.(string) } importPath = QualifyPackagePath(importPath) - p, _ := packages.Load(nil, importPath) + p, _ := packages.Load(&packages.Config{ + Mode: packages.NeedName, + }, importPath) if len(p) != 1 || p[0].Name == "" { return SanitizePackageName(filepath.Base(importPath)) diff --git a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go index 508cc14d..bb400f1b 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go +++ b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go @@ -1,6 +1,7 @@ package modelgen import ( + "fmt" "go/types" "sort" @@ -110,6 +111,7 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { for _, field := range schemaType.Fields { var typ types.Type + fieldDef := schema.Types[field.Type.Name()] if cfg.Models.UserDefined(field.Type.Name()) { pkg, typeName := code.PkgAndType(cfg.Models[field.Type.Name()].Model[0]) @@ -118,7 +120,6 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { return err } } else { - fieldDef := schema.Types[field.Type.Name()] switch fieldDef.Kind { case ast.Scalar: // no user defined model, referencing a default scalar @@ -127,6 +128,7 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { nil, nil, ) + case ast.Interface, ast.Union: // no user defined model, referencing a generated interface type typ = types.NewNamed( @@ -134,13 +136,25 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { types.NewInterfaceType([]*types.Func{}, []types.Type{}), nil, ) - default: - // no user defined model, must reference another generated model + + case ast.Enum: + // no user defined model, must reference a generated enum typ = types.NewNamed( types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), nil, nil, ) + + case ast.Object, ast.InputObject: + // no user defined model, must reference a generated struct + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), + types.NewStruct(nil, nil), + nil, + ) + + default: + panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind)) } } @@ -149,9 +163,15 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { name = nameOveride } + typ = binder.CopyModifiersFromAst(field.Type, typ) + + if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { + typ = types.NewPointer(typ) + } + it.Fields = append(it.Fields, &Field{ Name: name, - Type: binder.CopyModifiersFromAst(field.Type, typ), + Type: typ, Description: field.Description, Tag: `json:"` + field.Name + `"`, }) @@ -205,3 +225,8 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { GeneratedHeader: true, }) } + +func isStruct(t types.Type) bool { + _, is := t.Underlying().(*types.Struct) + return is +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl index d06cf050..6df200ee 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl +++ b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl @@ -31,7 +31,7 @@ } {{- range $iface := .Implements }} - func ({{ $model.Name|go }}) Is{{ $iface }}() {} + func ({{ $model.Name|go }}) Is{{ $iface|go }}() {} {{- end }} {{- end}} |