Repository: 99designs/gqlgen
Pull request #3334 (2024-10-17 18:35:14)

print response.Data before panicking when invalid
This helped me to identify an issue otherwise hard to debug, where the code for some reason returns invalid JSON.


Patch:
diff --git a/_examples/go.mod b/_examples/go.mod
index 5b696c88..ef7a248a 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/rs/cors v1.11.1
 	github.com/stretchr/testify v1.9.0
 	github.com/vektah/dataloaden v0.3.0
-	github.com/vektah/gqlparser/v2 v2.5.17
+	github.com/vektah/gqlparser/v2 v2.5.18
 	golang.org/x/sync v0.8.0
 )
 
diff --git a/_examples/go.sum b/_examples/go.sum
index 0cb316a0..ffb07fd0 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -44,8 +44,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
 github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
-github.com/vektah/gqlparser/v2 v2.5.17 h1:9At7WblLV7/36nulgekUgIaqHZWn5hxqluxrxGUhOmI=
-github.com/vektah/gqlparser/v2 v2.5.17/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
+github.com/vektah/gqlparser/v2 v2.5.18 h1:zSND3GtutylAQ1JpWnTHcqtaRZjl+y3NROeW8vuNo6Y=
+github.com/vektah/gqlparser/v2 v2.5.18/go.mod h1:6HLzf7JKv9Fi3APymudztFQNmLXR5qJeEo6BOFcXVfc=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
 golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
diff --git a/go.mod b/go.mod
index 78de9417..10c88978 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
 	github.com/sosodev/duration v1.3.1
 	github.com/stretchr/testify v1.9.0
 	github.com/urfave/cli/v2 v2.27.5
-	github.com/vektah/gqlparser/v2 v2.5.17
+	github.com/vektah/gqlparser/v2 v2.5.18
 	golang.org/x/text v0.19.0
 	golang.org/x/tools v0.24.0
 	google.golang.org/protobuf v1.35.1
diff --git a/go.sum b/go.sum
index 2c4b41f2..867149e3 100644
--- a/go.sum
+++ b/go.sum
@@ -47,8 +47,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
 github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
-github.com/vektah/gqlparser/v2 v2.5.17 h1:9At7WblLV7/36nulgekUgIaqHZWn5hxqluxrxGUhOmI=
-github.com/vektah/gqlparser/v2 v2.5.17/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
+github.com/vektah/gqlparser/v2 v2.5.18 h1:zSND3GtutylAQ1JpWnTHcqtaRZjl+y3NROeW8vuNo6Y=
+github.com/vektah/gqlparser/v2 v2.5.18/go.mod h1:6HLzf7JKv9Fi3APymudztFQNmLXR5qJeEo6BOFcXVfc=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
diff --git a/graphql/handler/transport/http_multipart_mixed.go b/graphql/handler/transport/http_multipart_mixed.go
new file mode 100644
index 00000000..362cae51
--- /dev/null
+++ b/graphql/handler/transport/http_multipart_mixed.go
@@ -0,0 +1,160 @@
+package transport
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"mime"
+	"net/http"
+	"strings"
+
+	"github.com/vektah/gqlparser/v2/gqlerror"
+
+	"github.com/99designs/gqlgen/graphql"
+)
+
+// MultipartMixed is a transport that supports the multipart/mixed spec
+type MultipartMixed struct {
+	Boundary string
+}
+
+var _ graphql.Transport = MultipartMixed{}
+
+// Supports checks if the request supports the multipart/mixed spec
+// Might be worth check the spec required, but Apollo Client mislabel the spec in the headers.
+func (t MultipartMixed) Supports(r *http.Request) bool {
+	if !strings.Contains(r.Header.Get("Accept"), "multipart/mixed") {
+		return false
+	}
+	mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
+	if err != nil {
+		return false
+	}
+	return r.Method == http.MethodPost && mediaType == "application/json"
+}
+
+// Do implements the multipart/mixed spec as a multipart/mixed response
+func (t MultipartMixed) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) {
+	// Implements the multipart/mixed spec as a multipart/mixed response:
+	// * https://github.com/graphql/graphql-wg/blob/e4ef5f9d5997815d9de6681655c152b6b7838b4c/rfcs/DeferStream.md
+	//   2022/08/23 as implemented by gqlgen.
+	// * https://github.com/graphql/graphql-wg/blob/f22ea7748c6ebdf88fdbf770a8d9e41984ebd429/rfcs/DeferStream.md June 2023 Spec for the
+	//   `incremental` field
+	// Follows the format that is used in the Apollo Client tests:
+	// https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/__tests__/responseIterator.ts#L68
+	// Apollo Client, despite mentioning in its requests that they require the 2022 spec, it wants the
+	// `incremental` field to be an array of responses, not a single response. Theoretically we could
+	// batch responses in the `incremental` field, if we wanted to optimize this code.
+	ctx := r.Context()
+	flusher, ok := w.(http.Flusher)
+	if !ok {
+		SendErrorf(w, http.StatusInternalServerError, "streaming unsupported")
+		return
+	}
+	defer flusher.Flush()
+
+	w.Header().Set("Cache-Control", "no-cache")
+	w.Header().Set("Connection", "keep-alive")
+	// This header will be replaced below, but it's required in case we return errors.
+	w.Header().Set("Content-Type", "application/json")
+
+	boundary := t.Boundary
+	if boundary == "" {
+		boundary = "graphql"
+	}
+
+	params := &graphql.RawParams{}
+	start := graphql.Now()
+	params.Headers = r.Header
+	params.ReadTime = graphql.TraceTiming{
+		Start: start,
+		End:   graphql.Now(),
+	}
+
+	bodyString, err := getRequestBody(r)
+	if err != nil {
+		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)
+		return
+	}
+
+	bodyReader := io.NopCloser(strings.NewReader(bodyString))
+	if err = jsonDecode(bodyReader, &params); 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
+	}
+
+	rc, opErr := exec.CreateOperationContext(ctx, params)
+	ctx = graphql.WithOperationContext(ctx, rc)
+
+	// Example of the response format (note the new lines are important!):
+	// --graphql
+	// Content-Type: application/json
+	//
+	// {"data":{"apps":{"apps":[ .. ],"totalNumApps":161,"__typename":"AppsOutput"}},"hasNext":true}
+	//
+	// --graphql
+	// Content-Type: application/json
+	//
+	// {"incremental":[{"data":{"groupAccessCount":0},"label":"test","path":["apps","apps",7],"hasNext":true}],"hasNext":true}
+
+	if opErr != nil {
+		w.WriteHeader(statusFor(opErr))
+
+		resp := exec.DispatchError(ctx, opErr)
+		writeJson(w, resp)
+		return
+	}
+
+	w.Header().Set(
+		"Content-Type",
+		fmt.Sprintf(`multipart/mixed;boundary="%s";deferSpec=20220824`, boundary),
+	)
+
+	responses, ctx := exec.DispatchOperation(ctx, rc)
+	initialResponse := true
+	for {
+		response := responses(ctx)
+		if response == nil {
+			break
+		}
+
+		fmt.Fprintf(w, "--%s\r\n", boundary)
+		fmt.Fprintf(w, "Content-Type: application/json\r\n\r\n")
+
+		if initialResponse {
+			writeJson(w, response)
+			initialResponse = false
+		} else {
+			writeIncrementalJson(w, response, response.HasNext)
+		}
+		fmt.Fprintf(w, "\r\n\r\n")
+		flusher.Flush()
+	}
+}
+
+func writeIncrementalJson(w io.Writer, response *graphql.Response, hasNext *bool) {
+	// TODO: Remove this wrapper on response once gqlgen supports the 2023 spec
+	b, err := json.Marshal(struct {
+		Incremental []graphql.Response `json:"incremental"`
+		HasNext     *bool              `json:"hasNext"`
+	}{
+		Incremental: []graphql.Response{*response},
+		HasNext:     hasNext,
+	})
+	if err != nil {
+		panic(err)
+	}
+	w.Write(b)
+}
diff --git a/graphql/handler/transport/util.go b/graphql/handler/transport/util.go
index aca7207e..f2a0d357 100644
--- a/graphql/handler/transport/util.go
+++ b/graphql/handler/transport/util.go
@@ -13,7 +13,7 @@ import (
 func writeJson(w io.Writer, response *graphql.Response) {
 	b, err := json.Marshal(response)
 	if err != nil {
-		panic(err)
+		panic(fmt.Errorf("unable to marshal %s: %w", string(response.Data), err))
 	}
 	w.Write(b)
 }
diff --git a/integration/package-lock.json b/integration/package-lock.json
index cd3ce78b..f2ddaf62 100644
--- a/integration/package-lock.json
+++ b/integration/package-lock.json
@@ -19,9 +19,9 @@
                 "graphql-sse": "^2.5.3",
                 "graphql-ws": "^5.16.0",
                 "typescript": "^5.6.3",
-                "urql": "^4.1.0",
-                "vite": "^5.4.8",
-                "vitest": "^2.1.2"
+                "urql": "^4.2.0",
+                "vite": "^5.4.9",
+                "vitest": "^2.1.3"
             }
         },
         "node_modules/@0no-co/graphql.web": {
@@ -2878,13 +2878,13 @@
             }
         },
         "node_modules/@vitest/expect": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz",
-            "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz",
+            "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==",
             "dev": true,
             "dependencies": {
-                "@vitest/spy": "2.1.2",
-                "@vitest/utils": "2.1.2",
+                "@vitest/spy": "2.1.3",
+                "@vitest/utils": "2.1.3",
                 "chai": "^5.1.1",
                 "tinyrainbow": "^1.2.0"
             },
@@ -2893,12 +2893,12 @@
             }
         },
         "node_modules/@vitest/mocker": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz",
-            "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz",
+            "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==",
             "dev": true,
             "dependencies": {
-                "@vitest/spy": "^2.1.0-beta.1",
+                "@vitest/spy": "2.1.3",
                 "estree-walker": "^3.0.3",
                 "magic-string": "^0.30.11"
             },
@@ -2906,7 +2906,7 @@
                 "url": "https://opencollective.com/vitest"
             },
             "peerDependencies": {
-                "@vitest/spy": "2.1.2",
+                "@vitest/spy": "2.1.3",
                 "msw": "^2.3.5",
                 "vite": "^5.0.0"
             },
@@ -2920,9 +2920,9 @@
             }
         },
         "node_modules/@vitest/pretty-format": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz",
-            "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz",
+            "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==",
             "dev": true,
             "dependencies": {
                 "tinyrainbow": "^1.2.0"
@@ -2932,12 +2932,12 @@
             }
         },
         "node_modules/@vitest/runner": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz",
-            "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz",
+            "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==",
             "dev": true,
             "dependencies": {
-                "@vitest/utils": "2.1.2",
+                "@vitest/utils": "2.1.3",
                 "pathe": "^1.1.2"
             },
             "funding": {
@@ -2945,12 +2945,12 @@
             }
         },
         "node_modules/@vitest/snapshot": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz",
-            "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz",
+            "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==",
             "dev": true,
             "dependencies": {
-                "@vitest/pretty-format": "2.1.2",
+                "@vitest/pretty-format": "2.1.3",
                 "magic-string": "^0.30.11",
                 "pathe": "^1.1.2"
             },
@@ -2959,9 +2959,9 @@
             }
         },
         "node_modules/@vitest/spy": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz",
-            "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz",
+            "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==",
             "dev": true,
             "dependencies": {
                 "tinyspy": "^3.0.0"
@@ -2971,12 +2971,12 @@
             }
         },
         "node_modules/@vitest/utils": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz",
-            "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz",
+            "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==",
             "dev": true,
             "dependencies": {
-                "@vitest/pretty-format": "2.1.2",
+                "@vitest/pretty-format": "2.1.3",
                 "loupe": "^3.1.1",
                 "tinyrainbow": "^1.2.0"
             },
@@ -4223,15 +4223,6 @@
                 "node": "6.* || 8.* || >= 10.*"
             }
         },
-        "node_modules/get-func-name": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
-            "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
-            "dev": true,
-            "engines": {
-                "node": "*"
-            }
-        },
         "node_modules/get-intrinsic": {
             "version": "1.2.4",
             "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
@@ -5564,13 +5555,10 @@
             }
         },
         "node_modules/loupe": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz",
-            "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==",
-            "dev": true,
-            "dependencies": {
-                "get-func-name": "^2.0.1"
-            }
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+            "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+            "dev": true
         },
         "node_modules/lower-case": {
             "version": "2.0.2",
@@ -5600,9 +5588,9 @@
             }
         },
         "node_modules/magic-string": {
-            "version": "0.30.11",
-            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
-            "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+            "version": "0.30.12",
+            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
+            "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
             "dev": true,
             "dependencies": {
                 "@jridgewell/sourcemap-codec": "^1.5.0"
@@ -7128,9 +7116,9 @@
             "dev": true
         },
         "node_modules/urql": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/urql/-/urql-4.1.0.tgz",
-            "integrity": "sha512-NfbfTvxy1sM89EQAJWm89qJZihUWk7BSMfrWgfljFXLOf+e7RK7DtV/Tbg2+82HnCG2x3LcEOJenxiFSYEC+bw==",
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/urql/-/urql-4.2.0.tgz",
+            "integrity": "sha512-xj9KoMxdF3omd0QCHHS3SvgxDHZAmuAFgUcdNe0w75NeJDA4qesc2/M6CquefZ7Sod6uyImENKCKg+/2I7YvXQ==",
             "dev": true,
             "dependencies": {
                 "@urql/core": "^5.0.0",
@@ -7167,9 +7155,9 @@
             }
         },
         "node_modules/vite": {
-            "version": "5.4.8",
-            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
-            "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
+            "version": "5.4.9",
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
+            "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
             "dev": true,
             "dependencies": {
                 "esbuild": "^0.21.3",
@@ -7226,9 +7214,9 @@
             }
         },
         "node_modules/vite-node": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz",
-            "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz",
+            "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==",
             "dev": true,
             "dependencies": {
                 "cac": "^6.7.14",
@@ -7247,18 +7235,18 @@
             }
         },
         "node_modules/vitest": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz",
-            "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz",
+            "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==",
             "dev": true,
             "dependencies": {
-                "@vitest/expect": "2.1.2",
-                "@vitest/mocker": "2.1.2",
-                "@vitest/pretty-format": "^2.1.2",
-                "@vitest/runner": "2.1.2",
-                "@vitest/snapshot": "2.1.2",
-                "@vitest/spy": "2.1.2",
-                "@vitest/utils": "2.1.2",
+                "@vitest/expect": "2.1.3",
+                "@vitest/mocker": "2.1.3",
+                "@vitest/pretty-format": "^2.1.3",
+                "@vitest/runner": "2.1.3",
+                "@vitest/snapshot": "2.1.3",
+                "@vitest/spy": "2.1.3",
+                "@vitest/utils": "2.1.3",
                 "chai": "^5.1.1",
                 "debug": "^4.3.6",
                 "magic-string": "^0.30.11",
@@ -7269,7 +7257,7 @@
                 "tinypool": "^1.0.0",
                 "tinyrainbow": "^1.2.0",
                 "vite": "^5.0.0",
-                "vite-node": "2.1.2",
+                "vite-node": "2.1.3",
                 "why-is-node-running": "^2.3.0"
             },
             "bin": {
@@ -7284,8 +7272,8 @@
             "peerDependencies": {
                 "@edge-runtime/vm": "*",
                 "@types/node": "^18.0.0 || >=20.0.0",
-                "@vitest/browser": "2.1.2",
-                "@vitest/ui": "2.1.2",
+                "@vitest/browser": "2.1.3",
+                "@vitest/ui": "2.1.3",
                 "happy-dom": "*",
                 "jsdom": "*"
             },
@@ -9329,74 +9317,74 @@
             }
         },
         "@vitest/expect": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz",
-            "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz",
+            "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==",
             "dev": true,
             "requires": {
-                "@vitest/spy": "2.1.2",
-                "@vitest/utils": "2.1.2",
+                "@vitest/spy": "2.1.3",
+                "@vitest/utils": "2.1.3",
                 "chai": "^5.1.1",
                 "tinyrainbow": "^1.2.0"
             }
         },
         "@vitest/mocker": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz",
-            "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz",
+            "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==",
             "dev": true,
             "requires": {
-                "@vitest/spy": "^2.1.0-beta.1",
+                "@vitest/spy": "2.1.3",
                 "estree-walker": "^3.0.3",
                 "magic-string": "^0.30.11"
             }
         },
         "@vitest/pretty-format": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz",
-            "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz",
+            "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==",
             "dev": true,
             "requires": {
                 "tinyrainbow": "^1.2.0"
             }
         },
         "@vitest/runner": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz",
-            "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz",
+            "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==",
             "dev": true,
             "requires": {
-                "@vitest/utils": "2.1.2",
+                "@vitest/utils": "2.1.3",
                 "pathe": "^1.1.2"
             }
         },
         "@vitest/snapshot": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz",
-            "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz",
+            "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==",
             "dev": true,
             "requires": {
-                "@vitest/pretty-format": "2.1.2",
+                "@vitest/pretty-format": "2.1.3",
                 "magic-string": "^0.30.11",
                 "pathe": "^1.1.2"
             }
         },
         "@vitest/spy": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz",
-            "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz",
+            "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==",
             "dev": true,
             "requires": {
                 "tinyspy": "^3.0.0"
             }
         },
         "@vitest/utils": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz",
-            "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz",
+            "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==",
             "dev": true,
             "requires": {
-                "@vitest/pretty-format": "2.1.2",
+                "@vitest/pretty-format": "2.1.3",
                 "loupe": "^3.1.1",
                 "tinyrainbow": "^1.2.0"
             }
@@ -10334,12 +10322,6 @@
             "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
             "dev": true
         },
-        "get-func-name": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
-            "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
-            "dev": true
-        },
         "get-intrinsic": {
             "version": "1.2.4",
             "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
@@ -11397,13 +11379,10 @@
             }
         },
         "loupe": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz",
-            "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==",
-            "dev": true,
-            "requires": {
-                "get-func-name": "^2.0.1"
-            }
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+            "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+            "dev": true
         },
         "lower-case": {
             "version": "2.0.2",
@@ -11433,9 +11412,9 @@
             }
         },
         "magic-string": {
-            "version": "0.30.11",
-            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
-            "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+            "version": "0.30.12",
+            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
+            "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
             "dev": true,
             "requires": {
                 "@jridgewell/sourcemap-codec": "^1.5.0"
@@ -12562,9 +12541,9 @@
             "dev": true
         },
         "urql": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/urql/-/urql-4.1.0.tgz",
-            "integrity": "sha512-NfbfTvxy1sM89EQAJWm89qJZihUWk7BSMfrWgfljFXLOf+e7RK7DtV/Tbg2+82HnCG2x3LcEOJenxiFSYEC+bw==",
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/urql/-/urql-4.2.0.tgz",
+            "integrity": "sha512-xj9KoMxdF3omd0QCHHS3SvgxDHZAmuAFgUcdNe0w75NeJDA4qesc2/M6CquefZ7Sod6uyImENKCKg+/2I7YvXQ==",
             "dev": true,
             "requires": {
                 "@urql/core": "^5.0.0",
@@ -12594,9 +12573,9 @@
             "dev": true
         },
         "vite": {
-            "version": "5.4.8",
-            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
-            "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
+            "version": "5.4.9",
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
+            "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
             "dev": true,
             "requires": {
                 "esbuild": "^0.21.3",
@@ -12606,9 +12585,9 @@
             }
         },
         "vite-node": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz",
-            "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz",
+            "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==",
             "dev": true,
             "requires": {
                 "cac": "^6.7.14",
@@ -12618,18 +12597,18 @@
             }
         },
         "vitest": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz",
-            "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==",
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz",
+            "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==",
             "dev": true,
             "requires": {
-                "@vitest/expect": "2.1.2",
-                "@vitest/mocker": "2.1.2",
-                "@vitest/pretty-format": "^2.1.2",
-                "@vitest/runner": "2.1.2",
-                "@vitest/snapshot": "2.1.2",
-                "@vitest/spy": "2.1.2",
-                "@vitest/utils": "2.1.2",
+                "@vitest/expect": "2.1.3",
+                "@vitest/mocker": "2.1.3",
+                "@vitest/pretty-format": "^2.1.3",
+                "@vitest/runner": "2.1.3",
+                "@vitest/snapshot": "2.1.3",
+                "@vitest/spy": "2.1.3",
+                "@vitest/utils": "2.1.3",
                 "chai": "^5.1.1",
                 "debug": "^4.3.6",
                 "magic-string": "^0.30.11",
@@ -12640,7 +12619,7 @@
                 "tinypool": "^1.0.0",
                 "tinyrainbow": "^1.2.0",
                 "vite": "^5.0.0",
-                "vite-node": "2.1.2",
+                "vite-node": "2.1.3",
                 "why-is-node-running": "^2.3.0"
             }
         },
diff --git a/integration/package.json b/integration/package.json
index 1d407a76..3a758fcb 100644
--- a/integration/package.json
+++ b/integration/package.json
@@ -19,8 +19,8 @@
         "graphql-sse": "^2.5.3",
         "graphql-ws": "^5.16.0",
         "typescript": "^5.6.3",
-        "urql": "^4.1.0",
-        "vite": "^5.4.8",
-        "vitest": "^2.1.2"
+        "urql": "^4.2.0",
+        "vite": "^5.4.9",
+        "vitest": "^2.1.3"
     }
 }


Test patch:
diff --git a/graphql/handler/testserver/testserver.go b/graphql/handler/testserver/testserver.go
index 33713b26..9cd6eeb7 100644
--- a/graphql/handler/testserver/testserver.go
+++ b/graphql/handler/testserver/testserver.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/vektah/gqlparser/v2"
@@ -43,6 +44,32 @@ func New() *TestServer {
 			switch rc.Operation.Operation {
 			case ast.Query:
 				ran := false
+				// If the query contains @defer, we will mimick a deferred response.
+				if strings.Contains(rc.RawQuery, "@defer") {
+					initialResponse := true
+					return func(context context.Context) *graphql.Response {
+						select {
+						case <-ctx.Done():
+							return nil
+						case <-next:
+							if initialResponse {
+								initialResponse = false
+								hasNext := true
+								return &graphql.Response{
+									Data:    []byte(`{"name":null}`),
+									HasNext: &hasNext,
+								}
+							}
+							hasNext := false
+							return &graphql.Response{
+								Data:    []byte(`{"name":"test"}`),
+								HasNext: &hasNext,
+							}
+						case <-completeSubscription:
+							return nil
+						}
+					}
+				}
 				return func(ctx context.Context) *graphql.Response {
 					if ran {
 						return nil
@@ -59,9 +86,10 @@ func New() *TestServer {
 							},
 						},
 					})
-					res, err := graphql.GetOperationContext(ctx).ResolverMiddleware(ctx, func(ctx context.Context) (any, error) {
-						return &graphql.Response{Data: []byte(`{"name":"test"}`)}, nil
-					})
+					res, err := graphql.GetOperationContext(ctx).
+						ResolverMiddleware(ctx, func(ctx context.Context) (any, error) {
+							return &graphql.Response{Data: []byte(`{"name":"test"}`)}, nil
+						})
 					if err != nil {
 						panic(err)
 					}
diff --git a/graphql/handler/transport/http_multipart_mixed_test.go b/graphql/handler/transport/http_multipart_mixed_test.go
new file mode 100644
index 00000000..e590074e
--- /dev/null
+++ b/graphql/handler/transport/http_multipart_mixed_test.go
@@ -0,0 +1,168 @@
+package transport_test
+
+import (
+	"bufio"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"sync"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/99designs/gqlgen/graphql/handler/testserver"
+	"github.com/99designs/gqlgen/graphql/handler/transport"
+)
+
+func TestMultipartMixed(t *testing.T) {
+	initialize := func() *testserver.TestServer {
+		h := testserver.New()
+		h.AddTransport(transport.MultipartMixed{})
+		return h
+	}
+
+	initializeWithServer := func() (*testserver.TestServer, *httptest.Server) {
+		h := initialize()
+		return h, httptest.NewServer(h)
+	}
+
+	createHTTPRequest := func(url string, query string) *http.Request {
+		req, err := http.NewRequest("POST", url, strings.NewReader(query))
+		require.NoError(t, err, "Request threw error -> %s", err)
+		req.Header.Set("Accept", "multipart/mixed")
+		req.Header.Set("content-type", "application/json; charset=utf-8")
+		return req
+	}
+
+	doRequest := func(handler http.Handler, target, body string) *httptest.ResponseRecorder {
+		r := createHTTPRequest(target, body)
+		w := httptest.NewRecorder()
+
+		handler.ServeHTTP(w, r)
+		return w
+	}
+
+	t.Run("decode failure", func(t *testing.T) {
+		handler, srv := initializeWithServer()
+		resp := doRequest(handler, srv.URL, "notjson")
+		assert.Equal(t, http.StatusBadRequest, resp.Code, resp.Body.String())
+		assert.Equal(t, "application/json", resp.Header().Get("Content-Type"))
+		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) {
+		handler, srv := initializeWithServer()
+		resp := doRequest(handler, srv.URL, `{"query": "!"}`)
+		assert.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String())
+		assert.Equal(t, "application/json", resp.Header().Get("Content-Type"))
+		assert.Equal(
+			t,
+			`{"errors":[{"message":"Unexpected !","locations":[{"line":1,"column":1}],"extensions":{"code":"GRAPHQL_PARSE_FAILED"}}],"data":null}`,
+			resp.Body.String(),
+		)
+	})
+
+	t.Run("validation failure", func(t *testing.T) {
+		handler, srv := initializeWithServer()
+		resp := doRequest(handler, srv.URL, `{"query": "{ title }"}`)
+		assert.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String())
+		assert.Equal(t, "application/json", resp.Header().Get("Content-Type"))
+		assert.Equal(
+			t,
+			`{"errors":[{"message":"Cannot query field \"title\" on type \"Query\".","locations":[{"line":1,"column":3}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}],"data":null}`,
+			resp.Body.String(),
+		)
+	})
+
+	t.Run("invalid variable", func(t *testing.T) {
+		handler, srv := initializeWithServer()
+		resp := doRequest(handler, srv.URL,
+			`{"query": "query($id:Int!){find(id:$id)}","variables":{"id":false}}`,
+		)
+		assert.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String())
+		assert.Equal(t, "application/json", resp.Header().Get("Content-Type"))
+		assert.Equal(
+			t,
+			`{"errors":[{"message":"cannot use bool as Int","path":["variable","id"],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}],"data":null}`,
+			resp.Body.String(),
+		)
+	})
+
+	readLine := func(br *bufio.Reader) string {
+		bs, err := br.ReadString('\n')
+		require.NoError(t, err)
+		return bs
+	}
+
+	t.Run("initial and incremental patches", func(t *testing.T) {
+		handler, srv := initializeWithServer()
+		defer srv.Close()
+
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			handler.SendNextSubscriptionMessage()
+		}()
+
+		client := &http.Client{}
+		req := createHTTPRequest(
+			srv.URL,
+			`{"query":"query { ... @defer { name } }"}`,
+		)
+		res, err := client.Do(req)
+		require.NoError(t, err, "Request threw error -> %s", err)
+		defer func() {
+			require.NoError(t, res.Body.Close())
+		}()
+
+		assert.Equal(t, 200, res.StatusCode, "Request return wrong status -> %d", res.Status)
+		assert.Equal(t, "keep-alive", res.Header.Get("Connection"))
+		assert.Contains(t, res.Header.Get("Content-Type"), "multipart/mixed")
+		assert.Contains(t, res.Header.Get("Content-Type"), `boundary="graphql"`)
+
+		br := bufio.NewReader(res.Body)
+
+		assert.Equal(t, "--graphql\r\n", readLine(br))
+		assert.Equal(t, "Content-Type: application/json\r\n", readLine(br))
+		assert.Equal(t, "\r\n", readLine(br))
+		assert.Equal(t,
+			"{\"data\":{\"name\":null},\"hasNext\":true}\r\n",
+			readLine(br),
+		)
+		assert.Equal(t, "\r\n", readLine(br))
+
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			handler.SendNextSubscriptionMessage()
+		}()
+
+		assert.Equal(t, "--graphql\r\n", readLine(br))
+		assert.Equal(t, "Content-Type: application/json\r\n", readLine(br))
+		assert.Equal(t, "\r\n", readLine(br))
+		assert.Equal(
+			t,
+			"{\"incremental\":[{\"data\":{\"name\":\"test\"},\"hasNext\":false}],\"hasNext\":false}\r\n",
+			readLine(br),
+		)
+		assert.Equal(t, "\r\n", readLine(br))
+
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			handler.SendCompleteSubscriptionMessage()
+		}()
+
+		_, err = br.ReadByte()
+		assert.Equal(t, err, io.EOF)
+
+		wg.Wait()
+	})
+}