diff --git a/graphql/handler/transport/http_post.go b/graphql/handler/transport/http_post.go index 99c3157185..28847754b9 100644 --- a/graphql/handler/transport/http_post.go +++ b/graphql/handler/transport/http_post.go @@ -1,8 +1,14 @@ package transport import ( + "fmt" + "io" + "log" "mime" "net/http" + "strings" + + "github.com/vektah/gqlparser/v2/gqlerror" "github.com/99designs/gqlgen/graphql" ) @@ -26,31 +32,59 @@ func (h POST) Supports(r *http.Request) bool { return r.Method == "POST" && mediaType == "application/json" } +func getRequestBody(r *http.Request) (string, error) { + if r == nil || r.Body == nil { + return "", nil + } + body, err := io.ReadAll(r.Body) + if err != nil { + return "", fmt.Errorf("unable to get Request Body %w", err) + } + return string(body), nil +} + func (h POST) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { + ctx := r.Context() w.Header().Set("Content-Type", "application/json") - - var params *graphql.RawParams + params := &graphql.RawParams{} start := graphql.Now() - if err := jsonDecode(r.Body, ¶ms); err != nil { - w.WriteHeader(http.StatusBadRequest) - writeJsonErrorf(w, "json body could not be decoded: "+err.Error()) - return - } - params.Headers = r.Header - params.ReadTime = graphql.TraceTiming{ Start: start, End: graphql.Now(), } - rc, err := exec.CreateOperationContext(r.Context(), params) + bodyString, err := getRequestBody(r) if err != nil { - w.WriteHeader(statusFor(err)) - resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), err) + gqlErr := gqlerror.Errorf("could not get json request body: %+v", err) + resp := exec.DispatchError(ctx, gqlerror.List{gqlErr}) + log.Printf("could not get json request body: %+v", err.Error()) + writeJson(w, resp) + } + + bodyReader := io.NopCloser(strings.NewReader(bodyString)) + if err = jsonDecode(bodyReader, ¶ms); err != nil { + w.WriteHeader(http.StatusBadRequest) + gqlErr := gqlerror.Errorf( + "json request body could not be decoded: %+v body:%s", + err, + bodyString, + ) + resp := exec.DispatchError(ctx, gqlerror.List{gqlErr}) + log.Printf("decoding error: %+v body:%s", err.Error(), bodyString) writeJson(w, resp) return } - responses, ctx := exec.DispatchOperation(r.Context(), rc) + + rc, OpErr := exec.CreateOperationContext(ctx, params) + if OpErr != nil { + w.WriteHeader(statusFor(OpErr)) + resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), OpErr) + writeJson(w, resp) + return + } + + var responses graphql.ResponseHandler + responses, ctx = exec.DispatchOperation(ctx, rc) writeJson(w, responses(ctx)) } diff --git a/graphql/handler/transport/http_post_test.go b/graphql/handler/transport/http_post_test.go index b27ffb3977..b104118e6f 100644 --- a/graphql/handler/transport/http_post_test.go +++ b/graphql/handler/transport/http_post_test.go @@ -26,7 +26,7 @@ func TestPOST(t *testing.T) { resp := doRequest(h, "POST", "/graphql", "notjson") assert.Equal(t, http.StatusBadRequest, resp.Code, resp.Body.String()) assert.Equal(t, resp.Header().Get("Content-Type"), "application/json") - assert.Equal(t, `{"errors":[{"message":"json body could not be decoded: invalid character 'o' in literal null (expecting 'u')"}],"data":null}`, resp.Body.String()) + assert.Equal(t, `{"errors":[{"message":"json request body could not be decoded: invalid character 'o' in literal null (expecting 'u') body:notjson"}],"data":null}`, resp.Body.String()) }) t.Run("parse failure", func(t *testing.T) { diff --git a/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go b/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go index c1412cb918..d7917ee998 100644 --- a/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go +++ b/plugin/resolvergen/testdata/filetemplate/out/schema.custom.go @@ -2,6 +2,7 @@ package customresolver // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.20-dev DO NOT EDIT. import ( "context" @@ -35,9 +36,9 @@ type resolverCustomResolverType struct{ *CustomResolverType } // !!! WARNING !!! // The code below was going to be deleted when updating resolvers. It has been copied here so you have // one last chance to move it out of harms way if you want. There are two reasons this happens: -// - When renaming or deleting a resolver the old code will be put in here. You can safely delete -// it when you're done. -// - You have helper methods in this file. Move them out to keep these resolver files clean. +// - When renaming or deleting a resolver the old code will be put in here. You can safely delete +// it when you're done. +// - You have helper methods in this file. Move them out to keep these resolver files clean. func AUserHelperFunction() { // AUserHelperFunction implementation } diff --git a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go index c1412cb918..d7917ee998 100644 --- a/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go +++ b/plugin/resolvergen/testdata/followschema/out/schema.resolvers.go @@ -2,6 +2,7 @@ package customresolver // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.20-dev DO NOT EDIT. import ( "context" @@ -35,9 +36,9 @@ type resolverCustomResolverType struct{ *CustomResolverType } // !!! WARNING !!! // The code below was going to be deleted when updating resolvers. It has been copied here so you have // one last chance to move it out of harms way if you want. There are two reasons this happens: -// - When renaming or deleting a resolver the old code will be put in here. You can safely delete -// it when you're done. -// - You have helper methods in this file. Move them out to keep these resolver files clean. +// - When renaming or deleting a resolver the old code will be put in here. You can safely delete +// it when you're done. +// - You have helper methods in this file. Move them out to keep these resolver files clean. func AUserHelperFunction() { // AUserHelperFunction implementation }