diff options
author | Amine Hilaly <hilalyamine@gmail.com> | 2019-05-15 15:04:57 +0200 |
---|---|---|
committer | Amine Hilaly <hilalyamine@gmail.com> | 2019-05-15 15:04:57 +0200 |
commit | 6949d6c543e9397578c7c840812df9bbf8531528 (patch) | |
tree | cdbc6c797b406029f03b5e5391fe1afdea88ce8e /vendor/github.com/99designs/gqlgen/handler | |
parent | 97476ff5fadaf0a457d0f0133db58415b6075940 (diff) | |
download | git-bug-6949d6c543e9397578c7c840812df9bbf8531528.tar.gz |
Upgrade gqlgen version to v0.9.0
Diffstat (limited to 'vendor/github.com/99designs/gqlgen/handler')
-rw-r--r-- | vendor/github.com/99designs/gqlgen/handler/graphql.go | 220 | ||||
-rw-r--r-- | vendor/github.com/99designs/gqlgen/handler/mock.go | 57 |
2 files changed, 274 insertions, 3 deletions
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 + } +} |