Skip to content

Commit

Permalink
Merge cc49b8a into c96021a
Browse files Browse the repository at this point in the history
  • Loading branch information
lucassabreu committed Jun 22, 2018
2 parents c96021a + cc49b8a commit 2c62376
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
gin-bin
coverage.out
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
language: go

go:
- 1.9.x
- 1.10.x
- tip

script:
- make install
- make coverage

after_success:
- make send-statistics commit=$TRAVIS_COMMIT

notifications:
email:
on_success: never
on_failure: always

env:
global:
secure: RW12+yXeYPkvpYnVO1MiLVyTW3LdBqwL5ZgnVoSmDfIAkySmViXsGUji5/nmAWaIWxOqAbOGHeJPRtM6cCUAKSk6kibPTW+mYNWxHaT0j81MRonrFY/3MnPB05JOjyr13zIdwhgsqXvToUSF615lLoSoHuhLlcH+mJR2PF2svav6rkyIrzcPaIz/mstEOQgsVceOY6PQobSRaEh6bSaUDgnCiklNhzgrGdh/idhBft5wD7N07UQ5YQICAGpV3MX5B9HbD2NtCPvxGVDwa8huKP05YcJJ+pKc4BxJLpLdxVE8jgFZ8eE0H6ZmkGrhJHpy2aT053M0P9ctk/JopuvxZHe82FiJnlBxdL3p+k0Cb3bi6JqoDzivKRT6A5G6fM/DeJckU+1dAssDsuCPGIKVH0G0D4fxfCGhG29ljNnsoGkvcAFyQu6XoeEeiDwwqrm80yMmHF8jndJqR0bjZmk7mnFbMCtzRCbwpp5mZQWV7dNgNC4zoWfdy/HraQUihThc4D9uyWvrTt6vw/c5Jz0PoI3dHJH3LEnIiOABCCd+s63Z876qLr/u0hgvfGbKi7qxocBHNdECNkpXKTiy9qZy5BDNP7bkom7ekmMkJdaxJc6HjAmZYTwi/gxrhTLvx6gr15yg8puE1C03XjE1egmnjs6riO4tqduWqdRtQb7y35s=
21 changes: 17 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
all: help

export PORT ?= 8000

# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help: ## show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

install:
go get -u -v ./...
install: ## install package dependencies
go get -u -v -t ./...

serve-example:
serve-example: ## start the example server
go run examples/main.go

serve-watch-example:
serve-watch-example: ## start the example server watching for changes
go get github.com/codegangsta/gin
PORT=8001 gin --port ${PORT} --appPort 8001 --build ./examples

tests: ## run the package's tests
go test -v -race .

coverage: ## calcs the coverage for the package
go get golang.org/x/tools/cmd/cover
go get github.com/mattn/goveralls
go test -v -covermode=count -coverprofile=coverage.out

send-statistics: ## send statistics
goveralls -coverprofile=coverage.out -service=travis-ci -repotoken ${COVERALLS_TOKEN}

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ GraphQL Multipart Middleware
[![](https://img.shields.io/badge/godoc-reference-5272B4.svg)](https://godoc.org/github.com/lucassabreu/graphql-multipart-middleware)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/1f0199e1dd364abcae45fd1f3de3cc25)](https://www.codacy.com/app/lucassabreu/graphql-multipart-middleware?utm_source=github.com&utm_medium=referral&utm_content=lucassabreu/graphql-multipart-middleware&utm_campaign=Badge_Grade)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware?ref=badge_shield)
[![Build Status](https://travis-ci.org/lucassabreu/graphql-multipart-middleware.svg?branch=master)](https://travis-ci.org/lucassabreu/graphql-multipart-middleware)
[![Coverage Status](https://coveralls.io/repos/github/lucassabreu/graphql-multipart-middleware/badge.svg?branch=master)](https://coveralls.io/github/lucassabreu/graphql-multipart-middleware?branch=master)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware?ref=badge_shield)

This packages provide a implementation of the graphql multipart request spec created by [@jaydenseric](https://github.com/jaydenseric) to provide support for handling file uploads in a GraphQL server, [click here to see the spec](https://github.com/jaydenseric/graphql-multipart-request-spec).

Expand All @@ -14,4 +17,4 @@ The package also provide a scalar for the uploaded content called `graphqlmultip


## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Flucassabreu%2Fgraphql-multipart-middleware?ref=badge_large)
64 changes: 40 additions & 24 deletions handler.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This packages provide a implementation of the graphql multipart request spec created by [@jaydenseric](https://github.com/jaydenseric) to provide support for handling file uploads in a GraphQL server, [click here to see the spec](https://github.com/jaydenseric/graphql-multipart-request-spec).
// Package graphqlmultipart provide a implementation of the graphql multipart request spec created by [@jaydenseric](https://github.com/jaydenseric) to provide support for handling file uploads in a GraphQL server, [click here to see the spec](https://github.com/jaydenseric/graphql-multipart-request-spec).
//
// Using the methods `graphqlmultipart.NewHandler` or `graphqlmultipart.NewMiddlewareWrapper` you will be abble to wrap your GraphQL handler and so every request made with the `Content-Type`: `multipart/form-data` will be handled by this package (using a provided GraphQL schema), and other `Content-Types` will be directed to your handler.
//
Expand All @@ -21,19 +21,26 @@ import (
const specURL = "https://github.com/jaydenseric/graphql-multipart-request-spec/tree/v2.0.0"

var (
failedToParseForm = "Failed to parse multipart form"
// FailedToParseFormMessage is shown when it is a multipart/form-data, but its invalid
FailedToParseFormMessage = "Failed to parse multipart form"

operationsFieldMissingMessage = fmt.Sprintf("Field \"operations\" was not found in the form (%s)", specURL)
// OperationsFieldMissingMessage is shown when the operations field is missing
OperationsFieldMissingMessage = fmt.Sprintf("Field \"operations\" was not found in the form (%s)", specURL)

mapFieldMissingMessage = fmt.Sprintf("Field \"map\" was not found in the form (%s)", specURL)
// MapFieldMissingMessage is shown when the map field is missing
MapFieldMissingMessage = fmt.Sprintf("Field \"map\" was not found in the form (%s)", specURL)

invalidMapFieldMessage = fmt.Sprintf("Field \"map\" format is not valid (%s)", specURL)
// InvalidMapFieldMessage is shown when the map field format is invalid
InvalidMapFieldMessage = fmt.Sprintf("Field \"map\" format is not valid (%s)", specURL)

invalidOperationsFieldMessage = fmt.Sprintf("Field \"operations\" format is not valid (%s)", specURL)
// InvalidOperationsFieldMessage is shown when operations field format is not valid
InvalidOperationsFieldMessage = fmt.Sprintf("Field \"operations\" format is not valid (%s)", specURL)

missingFileMessage = fmt.Sprintf("Field %%[1]s is missing, but exists in the map association (%s)", specURL)
// MissingFileMessage is shown when a file is mapped, but not sent
MissingFileMessage = fmt.Sprintf("File \"%%[1]s\" is missing, but exists in the map association (%s)", specURL)

invalidMapPathMessage = fmt.Sprintf("Invalid mapping path \"%%[1]s\" for file %%[2]s (%s)", specURL)
// InvalidMapPathMessage is shown when is not possible to find or populate the variable path
InvalidMapPathMessage = fmt.Sprintf("Invalid mapping path \"%%[1]s\" for file %%[2]s (%s)", specURL)
)

// MultipartHandler implements the specification for handling multipart/form-data
Expand Down Expand Up @@ -64,9 +71,9 @@ func NewMiddlewareWrapper(s *graphql.Schema, maxMemory int64) func(next http.Han
}

type operationField struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
OperationName string `json:"operationName"`
Query string `json:"query"`
Variables *map[string]interface{} `json:"variables"`
OperationName string `json:"operationName"`
mapPrefix string
}

Expand All @@ -82,7 +89,7 @@ func (m MultipartHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

if err := r.ParseMultipartForm(m.maxMemory); err != nil {
log.Printf("[MultipartHandler] Fail do parse multipart form: %s", err.Error())
writeError(w, failedToParseForm)
writeError(w, FailedToParseFormMessage)
return
}

Expand All @@ -92,31 +99,40 @@ func (m MultipartHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var ok bool

if vs, ok = form.Value["operations"]; !ok {
writeError(w, operationsFieldMissingMessage)
writeError(w, OperationsFieldMissingMessage)
return
}
opsStr := vs[0]

if vs, ok = form.Value["map"]; !ok {
writeError(w, mapFieldMissingMessage)
writeError(w, MapFieldMissingMessage)
return
}
fileMapStr := vs[0]

fileMap := make(map[string][]string)
if err := json.Unmarshal([]byte(fileMapStr), &fileMap); err != nil {
writeError(w, invalidMapFieldMessage)
writeError(w, InvalidMapFieldMessage)
return
}

batching := true
ops := make([]operationField, 1)
ops := make([]operationField, 0)
if err := json.Unmarshal([]byte(opsStr), &ops); err != nil {
batching = false
if err = json.Unmarshal([]byte(opsStr), &ops[0]); err != nil {
writeError(w, invalidOperationsFieldMessage)
op := operationField{}
err = json.Unmarshal([]byte(opsStr), &op)
if err != nil || len(op.Query) == 0 || op.Variables == nil {
writeError(w, InvalidOperationsFieldMessage)
return
}

ops = append(ops, op)
}

if len(ops) == 0 {
writeError(w, InvalidOperationsFieldMessage)
return
}

results := make([]*graphql.Result, len(ops))
Expand Down Expand Up @@ -147,7 +163,7 @@ func (m MultipartHandler) execute(op operationField, fMap map[string][]string, r
for f, ps := range fMap {

if _, ok := r.MultipartForm.File[f]; !ok {
errs = append(errs, fmt.Errorf(fmt.Sprintf(missingFileMessage, f)))
errs = append(errs, fmt.Errorf(fmt.Sprintf(MissingFileMessage, f)))
continue
}

Expand All @@ -158,15 +174,15 @@ func (m MultipartHandler) execute(op operationField, fMap map[string][]string, r

vars, ok := injectFile(
r.MultipartForm.File[f][0],
op.Variables,
*op.Variables,
p[len(op.mapPrefix):],
)

if !ok {
errs = append(errs, fmt.Errorf(invalidMapPathMessage, p, f))
errs = append(errs, fmt.Errorf(InvalidMapPathMessage, p, f))
continue
}
op.Variables = vars.(map[string]interface{})
*op.Variables = vars.(map[string]interface{})
}
}

Expand All @@ -179,7 +195,7 @@ func (m MultipartHandler) execute(op operationField, fMap map[string][]string, r
return graphql.Do(graphql.Params{
Schema: *m.Schema,
RequestString: op.Query,
VariableValues: op.Variables,
VariableValues: *op.Variables,
OperationName: op.OperationName,
Context: r.Context(),
})
Expand All @@ -189,7 +205,7 @@ func injectFile(f *multipart.FileHeader, vars interface{}, path string) (interfa
var field, next string

field = path
if i := strings.Index(".", path); i != -1 {
if i := strings.Index(path, "."); i != -1 {
field = path[0:i]
next = path[i+1:]
}
Expand Down

0 comments on commit 2c62376

Please sign in to comment.