@@ -19,6 +19,7 @@ import {
19
19
specifiedRules
20
20
} from 'graphql' ;
21
21
import httpError from 'http-errors' ;
22
+ import assign from 'object-assign' ;
22
23
import url from 'url' ;
23
24
24
25
import { parseBody } from './parseBody' ;
@@ -103,7 +104,7 @@ export default function graphqlHTTP(options: Options): Middleware {
103
104
let validationRules ;
104
105
105
106
// Promises are used as a mechanism for capturing any thrown errors during
106
- // the asyncronous process below.
107
+ // the asynchronous process below.
107
108
108
109
// Resolve the Options to get OptionsData.
109
110
new Promise ( resolve => {
@@ -150,102 +151,123 @@ export default function graphqlHTTP(options: Options): Middleware {
150
151
// Parse the Request body.
151
152
return parseBody ( request ) ;
152
153
} ) . then ( bodyData => {
153
- const urlData = request . url && url . parse ( request . url , true ) . query || { } ;
154
- showGraphiQL = graphiql && canDisplayGraphiQL ( request , urlData , bodyData ) ;
155
-
156
- // Get GraphQL params from the request and POST body data.
157
- const params = getGraphQLParams ( urlData , bodyData ) ;
158
- query = params . query ;
159
- variables = params . variables ;
160
- operationName = params . operationName ;
161
-
162
- // If there is no query, but GraphiQL will be displayed, do not produce
163
- // a result, otherwise return a 400: Bad Request.
164
- if ( ! query ) {
165
- if ( showGraphiQL ) {
166
- return null ;
154
+ function executeQuery ( requestData ) {
155
+ // Get GraphQL params from the request and POST body data.
156
+ const params = getGraphQLParams ( requestData ) ;
157
+ query = params . query ;
158
+ variables = params . variables ;
159
+ operationName = params . operationName ;
160
+
161
+ // If there is no query, but GraphiQL will be displayed, do not produce
162
+ // a result, otherwise return a 400: Bad Request.
163
+ if ( ! query ) {
164
+ if ( showGraphiQL ) {
165
+ return null ;
166
+ }
167
+ throw httpError ( 400 , 'Must provide query string.' ) ;
167
168
}
168
- throw httpError ( 400 , 'Must provide query string.' ) ;
169
- }
170
169
171
- // GraphQL source.
172
- const source = new Source ( query , 'GraphQL request' ) ;
173
-
174
- // Parse source to AST, reporting any syntax error.
175
- let documentAST ;
176
- try {
177
- documentAST = parse ( source ) ;
178
- } catch ( syntaxError ) {
179
- // Return 400: Bad Request if any syntax errors errors exist.
180
- response . statusCode = 400 ;
181
- return { errors : [ syntaxError ] } ;
182
- }
170
+ // GraphQL source.
171
+ const source = new Source ( query , 'GraphQL request' ) ;
172
+
173
+ // Parse source to AST, reporting any syntax error.
174
+ let documentAST ;
175
+ try {
176
+ documentAST = parse ( source ) ;
177
+ } catch ( syntaxError ) {
178
+ // Return 400: Bad Request if any syntax errors errors exist.
179
+ response . statusCode = 400 ;
180
+ return { errors : [ syntaxError ] } ;
181
+ }
183
182
184
- // Validate AST, reporting any errors.
185
- const validationErrors = validate ( schema , documentAST , validationRules ) ;
186
- if ( validationErrors . length > 0 ) {
187
- // Return 400: Bad Request if any validation errors exist.
188
- response . statusCode = 400 ;
189
- return { errors : validationErrors } ;
190
- }
183
+ // Validate AST, reporting any errors.
184
+ const validationErrors = validate ( schema , documentAST , validationRules ) ;
185
+ if ( validationErrors . length > 0 ) {
186
+ // Return 400: Bad Request if any validation errors exist.
187
+ response . statusCode = 400 ;
188
+ return { errors : validationErrors } ;
189
+ }
191
190
192
- // Only query operations are allowed on GET requests.
193
- if ( request . method === 'GET' ) {
194
- // Determine if this GET request will perform a non-query.
195
- const operationAST = getOperationAST ( documentAST , operationName ) ;
196
- if ( operationAST && operationAST . operation !== 'query' ) {
197
- // If GraphiQL can be shown, do not perform this query, but
198
- // provide it to GraphiQL so that the requester may perform it
199
- // themselves if desired.
200
- if ( showGraphiQL ) {
201
- return null ;
191
+ // Only query operations are allowed on GET requests.
192
+ if ( request . method === 'GET' ) {
193
+ // Determine if this GET request will perform a non-query.
194
+ const operationAST = getOperationAST ( documentAST , operationName ) ;
195
+ if ( operationAST && operationAST . operation !== 'query' ) {
196
+ // If GraphiQL can be shown, do not perform this query, but
197
+ // provide it to GraphiQL so that the requester may perform it
198
+ // themselves if desired.
199
+ if ( showGraphiQL ) {
200
+ return null ;
201
+ }
202
+
203
+ // Otherwise, report a 405: Method Not Allowed error.
204
+ response . setHeader ( 'Allow' , 'POST' ) ;
205
+ throw httpError (
206
+ 405 ,
207
+ `Can only perform a ${ operationAST . operation } operation ` +
208
+ 'from a POST request.'
209
+ ) ;
202
210
}
211
+ }
203
212
204
- // Otherwise, report a 405: Method Not Allowed error.
205
- response . setHeader ( 'Allow' , 'POST' ) ;
206
- throw httpError (
207
- 405 ,
208
- `Can only perform a ${ operationAST . operation } operation ` +
209
- 'from a POST request.'
213
+ // Perform the execution, reporting any errors creating the context.
214
+ try {
215
+ return execute (
216
+ schema ,
217
+ documentAST ,
218
+ rootValue ,
219
+ context ,
220
+ variables ,
221
+ operationName
210
222
) ;
223
+ } catch ( contextError ) {
224
+ // Return 400: Bad Request if any execution context errors exist.
225
+ response . statusCode = 400 ;
226
+ return { errors : [ contextError ] } ;
211
227
}
212
228
}
213
- // Perform the execution, reporting any errors creating the context.
214
- try {
215
- return execute (
216
- schema ,
217
- documentAST ,
218
- rootValue ,
219
- context ,
220
- variables ,
221
- operationName
222
- ) ;
223
- } catch ( contextError ) {
224
- // Return 400: Bad Request if any execution context errors exist.
225
- response . statusCode = 400 ;
226
- return { errors : [ contextError ] } ;
229
+
230
+ if ( Array . isArray ( bodyData ) ) {
231
+ // Body is an array. This is a batched query, so don't show GraphiQL.
232
+ showGraphiQL = false ;
233
+ return Promise . all ( bodyData . map ( executeQuery ) ) ;
227
234
}
235
+
236
+ const urlData = request . url && url . parse ( request . url , true ) . query || { } ;
237
+ const requestData = assign ( urlData , bodyData ) ;
238
+ showGraphiQL = graphiql && canDisplayGraphiQL ( request , requestData ) ;
239
+
240
+ return executeQuery ( requestData ) ;
228
241
} ) . catch ( error => {
229
242
// If an error was caught, report the httpError status, or 500.
230
243
response . statusCode = error . status || 500 ;
231
244
return { errors : [ error ] } ;
232
- } ) . then ( result => {
245
+ } ) . then ( results => {
246
+ function formatResultErrors ( result ) {
247
+ if ( result && result . errors ) {
248
+ result . errors = result . errors . map ( formatErrorFn || formatError ) ;
249
+ }
250
+ }
251
+
233
252
// Format any encountered errors.
234
- if ( result && result . errors ) {
235
- result . errors = result . errors . map ( formatErrorFn || formatError ) ;
253
+ if ( Array . isArray ( results ) ) {
254
+ results . forEach ( formatResultErrors ) ;
255
+ } else {
256
+ formatResultErrors ( results ) ;
236
257
}
258
+
237
259
// If allowed to show GraphiQL, present it instead of JSON.
238
260
if ( showGraphiQL ) {
239
261
const data = renderGraphiQL ( {
240
262
query, variables,
241
- operationName, result
263
+ operationName, results
242
264
} ) ;
243
265
response . setHeader ( 'Content-Type' , 'text/html' ) ;
244
266
response . write ( data ) ;
245
267
response . end ( ) ;
246
268
} else {
247
269
// Otherwise, present JSON directly.
248
- const data = JSON . stringify ( result , null , pretty ? 2 : 0 ) ;
270
+ const data = JSON . stringify ( results , null , pretty ? 2 : 0 ) ;
249
271
response . setHeader ( 'Content-Type' , 'application/json' ) ;
250
272
response . write ( data ) ;
251
273
response . end ( ) ;
@@ -263,12 +285,12 @@ type GraphQLParams = {
263
285
/**
264
286
* Helper function to get the GraphQL params from the request.
265
287
*/
266
- function getGraphQLParams ( urlData : Object , bodyData : Object ) : GraphQLParams {
288
+ function getGraphQLParams ( requestData : Object ) : GraphQLParams {
267
289
// GraphQL Query string.
268
- const query = urlData . query || bodyData . query ;
290
+ const query = requestData . query ;
269
291
270
292
// Parse the variables if needed.
271
- let variables = urlData . variables || bodyData . variables ;
293
+ let variables = requestData . variables ;
272
294
if ( variables && typeof variables === 'string' ) {
273
295
try {
274
296
variables = JSON . parse ( variables ) ;
@@ -278,21 +300,17 @@ function getGraphQLParams(urlData: Object, bodyData: Object): GraphQLParams {
278
300
}
279
301
280
302
// Name of GraphQL operation to execute.
281
- const operationName = urlData . operationName || bodyData . operationName ;
303
+ const operationName = requestData . operationName ;
282
304
283
305
return { query, variables, operationName } ;
284
306
}
285
307
286
308
/**
287
309
* Helper function to determine if GraphiQL can be displayed.
288
310
*/
289
- function canDisplayGraphiQL (
290
- request : Request ,
291
- urlData : Object ,
292
- bodyData : Object
293
- ) : boolean {
311
+ function canDisplayGraphiQL ( request : Request , requestData : Object ) : boolean {
294
312
// If `raw` exists, GraphiQL mode is not enabled.
295
- const raw = urlData . raw !== undefined || bodyData . raw !== undefined ;
313
+ const raw = requestData . raw !== undefined ;
296
314
// Allowed to show GraphiQL if not requested as raw and this request
297
315
// prefers HTML over JSON.
298
316
return ! raw && accepts ( request ) . types ( [ 'json' , 'html' ] ) === 'html' ;
0 commit comments