1 | ;
|
2 |
|
3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /**
|
4 | * Copyright (c) 2015-present, Facebook, Inc.
|
5 | * All rights reserved.
|
6 | *
|
7 | * This source code is licensed under the BSD-style license found in the
|
8 | * LICENSE file in the root directory of this source tree. An additional grant
|
9 | * of patent rights can be found in the PATENTS file in the same directory.
|
10 | *
|
11 | * strict
|
12 | */
|
13 |
|
14 | var _accepts = require('accepts');
|
15 |
|
16 | var _accepts2 = _interopRequireDefault(_accepts);
|
17 |
|
18 | var _graphql = require('graphql');
|
19 |
|
20 | var _httpErrors = require('http-errors');
|
21 |
|
22 | var _httpErrors2 = _interopRequireDefault(_httpErrors);
|
23 |
|
24 | var _url = require('url');
|
25 |
|
26 | var _url2 = _interopRequireDefault(_url);
|
27 |
|
28 | var _parseBody = require('./parseBody');
|
29 |
|
30 | var _renderGraphiQL = require('./renderGraphiQL');
|
31 |
|
32 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
33 |
|
34 | /**
|
35 | * Middleware for express; takes an options object or function as input to
|
36 | * configure behavior, and returns an express middleware.
|
37 | */
|
38 |
|
39 |
|
40 | /**
|
41 | * Used to configure the graphqlHTTP middleware by providing a schema
|
42 | * and other configuration options.
|
43 | *
|
44 | * Options can be provided as an Object, a Promise for an Object, or a Function
|
45 | * that returns an Object or a Promise for an Object.
|
46 | */
|
47 |
|
48 |
|
49 | /**
|
50 | * All information about a GraphQL request.
|
51 | */
|
52 | module.exports = graphqlHTTP;
|
53 | function graphqlHTTP(options) {
|
54 | if (!options) {
|
55 | throw new Error('GraphQL middleware requires options.');
|
56 | }
|
57 |
|
58 | return function graphqlMiddleware(request, response) {
|
59 | // Higher scoped variables are referred to at various stages in the
|
60 | // asynchronous state machine below.
|
61 | var context = void 0;
|
62 | var params = void 0;
|
63 | var pretty = void 0;
|
64 | var formatErrorFn = void 0;
|
65 | var extensionsFn = void 0;
|
66 | var showGraphiQL = void 0;
|
67 | var query = void 0;
|
68 |
|
69 | var documentAST = void 0;
|
70 | var variables = void 0;
|
71 | var operationName = void 0;
|
72 |
|
73 | // Promises are used as a mechanism for capturing any thrown errors during
|
74 | // the asynchronous process below.
|
75 |
|
76 | // Parse the Request to get GraphQL request parameters.
|
77 | return getGraphQLParams(request).then(function (graphQLParams) {
|
78 | params = graphQLParams;
|
79 | // Then, resolve the Options to get OptionsData.
|
80 | return resolveOptions(params);
|
81 | }, function (error) {
|
82 | // When we failed to parse the GraphQL parameters, we still need to get
|
83 | // the options object, so make an options call to resolve just that.
|
84 | var dummyParams = {
|
85 | query: null,
|
86 | variables: null,
|
87 | operationName: null,
|
88 | raw: null
|
89 | };
|
90 | return resolveOptions(dummyParams).then(function () {
|
91 | return Promise.reject(error);
|
92 | });
|
93 | }).then(function (optionsData) {
|
94 | // Assert that schema is required.
|
95 | if (!optionsData.schema) {
|
96 | throw new Error('GraphQL middleware options must contain a schema.');
|
97 | }
|
98 |
|
99 | // Collect information from the options data object.
|
100 | var schema = optionsData.schema;
|
101 | var rootValue = optionsData.rootValue;
|
102 | var fieldResolver = optionsData.fieldResolver;
|
103 | var graphiql = optionsData.graphiql;
|
104 |
|
105 | context = optionsData.context || request;
|
106 |
|
107 | var validationRules = _graphql.specifiedRules;
|
108 | if (optionsData.validationRules) {
|
109 | validationRules = validationRules.concat(optionsData.validationRules);
|
110 | }
|
111 |
|
112 | // GraphQL HTTP only supports GET and POST methods.
|
113 | if (request.method !== 'GET' && request.method !== 'POST') {
|
114 | response.setHeader('Allow', 'GET, POST');
|
115 | throw (0, _httpErrors2.default)(405, 'GraphQL only supports GET and POST requests.');
|
116 | }
|
117 |
|
118 | // Get GraphQL params from the request and POST body data.
|
119 | query = params.query;
|
120 | variables = params.variables;
|
121 | operationName = params.operationName;
|
122 | showGraphiQL = graphiql && canDisplayGraphiQL(request, params);
|
123 |
|
124 | // If there is no query, but GraphiQL will be displayed, do not produce
|
125 | // a result, otherwise return a 400: Bad Request.
|
126 | if (!query) {
|
127 | if (showGraphiQL) {
|
128 | return null;
|
129 | }
|
130 | throw (0, _httpErrors2.default)(400, 'Must provide query string.');
|
131 | }
|
132 |
|
133 | // Validate Schema
|
134 | var schemaValidationErrors = (0, _graphql.validateSchema)(schema);
|
135 | if (schemaValidationErrors.length > 0) {
|
136 | // Return 500: Internal Server Error if invalid schema.
|
137 | response.statusCode = 500;
|
138 | return { errors: schemaValidationErrors };
|
139 | }
|
140 |
|
141 | // GraphQL source.
|
142 | var source = new _graphql.Source(query, 'GraphQL request');
|
143 |
|
144 | // Parse source to AST, reporting any syntax error.
|
145 | try {
|
146 | documentAST = (0, _graphql.parse)(source);
|
147 | } catch (syntaxError) {
|
148 | // Return 400: Bad Request if any syntax errors errors exist.
|
149 | response.statusCode = 400;
|
150 | return { errors: [syntaxError] };
|
151 | }
|
152 |
|
153 | // Validate AST, reporting any errors.
|
154 | var validationErrors = (0, _graphql.validate)(schema, documentAST, validationRules);
|
155 | if (validationErrors.length > 0) {
|
156 | // Return 400: Bad Request if any validation errors exist.
|
157 | response.statusCode = 400;
|
158 | return { errors: validationErrors };
|
159 | }
|
160 |
|
161 | // Only query operations are allowed on GET requests.
|
162 | if (request.method === 'GET') {
|
163 | // Determine if this GET request will perform a non-query.
|
164 | var operationAST = (0, _graphql.getOperationAST)(documentAST, operationName);
|
165 | if (operationAST && operationAST.operation !== 'query') {
|
166 | // If GraphiQL can be shown, do not perform this query, but
|
167 | // provide it to GraphiQL so that the requester may perform it
|
168 | // themselves if desired.
|
169 | if (showGraphiQL) {
|
170 | return null;
|
171 | }
|
172 |
|
173 | // Otherwise, report a 405: Method Not Allowed error.
|
174 | response.setHeader('Allow', 'POST');
|
175 | throw (0, _httpErrors2.default)(405, 'Can only perform a ' + operationAST.operation + ' operation ' + 'from a POST request.');
|
176 | }
|
177 | }
|
178 | // Perform the execution, reporting any errors creating the context.
|
179 | try {
|
180 | return (0, _graphql.execute)(schema, documentAST, rootValue, context, variables, operationName, fieldResolver);
|
181 | } catch (contextError) {
|
182 | // Return 400: Bad Request if any execution context errors exist.
|
183 | response.statusCode = 400;
|
184 | return { errors: [contextError] };
|
185 | }
|
186 | }).then(function (result) {
|
187 | // Collect and apply any metadata extensions if a function was provided.
|
188 | // http://facebook.github.io/graphql/#sec-Response-Format
|
189 | if (result && extensionsFn) {
|
190 | return Promise.resolve(extensionsFn({
|
191 | document: documentAST,
|
192 | variables: variables,
|
193 | operationName: operationName,
|
194 | result: result,
|
195 | context: context
|
196 | })).then(function (extensions) {
|
197 | if (extensions && (typeof extensions === 'undefined' ? 'undefined' : _typeof(extensions)) === 'object') {
|
198 | result.extensions = extensions;
|
199 | }
|
200 | return result;
|
201 | });
|
202 | }
|
203 | return result;
|
204 | }).catch(function (error) {
|
205 | // If an error was caught, report the httpError status, or 500.
|
206 | response.statusCode = error.status || 500;
|
207 | return { errors: [error] };
|
208 | }).then(function (result) {
|
209 | // If no data was included in the result, that indicates a runtime query
|
210 | // error, indicate as such with a generic status code.
|
211 | // Note: Information about the error itself will still be contained in
|
212 | // the resulting JSON payload.
|
213 | // http://facebook.github.io/graphql/#sec-Data
|
214 | if (response.statusCode === 200 && result && !result.data) {
|
215 | response.statusCode = 500;
|
216 | }
|
217 | // Format any encountered errors.
|
218 | if (result && result.errors) {
|
219 | result.errors = result.errors.map(formatErrorFn || _graphql.formatError);
|
220 | }
|
221 |
|
222 | // If allowed to show GraphiQL, present it instead of JSON.
|
223 | if (showGraphiQL) {
|
224 | var payload = (0, _renderGraphiQL.renderGraphiQL)({
|
225 | query: query,
|
226 | variables: variables,
|
227 | operationName: operationName,
|
228 | result: result
|
229 | });
|
230 | return sendResponse(response, 'text/html', payload);
|
231 | }
|
232 |
|
233 | // At this point, result is guaranteed to exist, as the only scenario
|
234 | // where it will not is when showGraphiQL is true.
|
235 | if (!result) {
|
236 | throw (0, _httpErrors2.default)(500, 'Internal Error');
|
237 | }
|
238 |
|
239 | // If "pretty" JSON isn't requested, and the server provides a
|
240 | // response.json method (express), use that directly.
|
241 | // Otherwise use the simplified sendResponse method.
|
242 | if (!pretty && typeof response.json === 'function') {
|
243 | response.json(result);
|
244 | } else {
|
245 | var _payload = JSON.stringify(result, null, pretty ? 2 : 0);
|
246 | sendResponse(response, 'application/json', _payload);
|
247 | }
|
248 | });
|
249 |
|
250 | function resolveOptions(requestParams) {
|
251 | return Promise.resolve(typeof options === 'function' ? options(request, response, requestParams) : options).then(function (optionsData) {
|
252 | // Assert that optionsData is in fact an Object.
|
253 | if (!optionsData || (typeof optionsData === 'undefined' ? 'undefined' : _typeof(optionsData)) !== 'object') {
|
254 | throw new Error('GraphQL middleware option function must return an options object ' + 'or a promise which will be resolved to an options object.');
|
255 | }
|
256 |
|
257 | formatErrorFn = optionsData.formatError;
|
258 | extensionsFn = optionsData.extensions;
|
259 | pretty = optionsData.pretty;
|
260 | return optionsData;
|
261 | });
|
262 | }
|
263 | };
|
264 | }
|
265 |
|
266 | /**
|
267 | * Provided a "Request" provided by express or connect (typically a node style
|
268 | * HTTPClientRequest), Promise the GraphQL request parameters.
|
269 | */
|
270 | module.exports.getGraphQLParams = getGraphQLParams;
|
271 | function getGraphQLParams(request) {
|
272 | return (0, _parseBody.parseBody)(request).then(function (bodyData) {
|
273 | var urlData = request.url && _url2.default.parse(request.url, true).query || {};
|
274 | return parseGraphQLParams(urlData, bodyData);
|
275 | });
|
276 | }
|
277 |
|
278 | /**
|
279 | * Helper function to get the GraphQL params from the request.
|
280 | */
|
281 | function parseGraphQLParams(urlData, bodyData) {
|
282 | // GraphQL Query string.
|
283 | var query = urlData.query || bodyData.query;
|
284 | if (typeof query !== 'string') {
|
285 | query = null;
|
286 | }
|
287 |
|
288 | // Parse the variables if needed.
|
289 | var variables = urlData.variables || bodyData.variables;
|
290 | if (variables && typeof variables === 'string') {
|
291 | try {
|
292 | variables = JSON.parse(variables);
|
293 | } catch (error) {
|
294 | throw (0, _httpErrors2.default)(400, 'Variables are invalid JSON.');
|
295 | }
|
296 | } else if ((typeof variables === 'undefined' ? 'undefined' : _typeof(variables)) !== 'object') {
|
297 | variables = null;
|
298 | }
|
299 |
|
300 | // Name of GraphQL operation to execute.
|
301 | var operationName = urlData.operationName || bodyData.operationName;
|
302 | if (typeof operationName !== 'string') {
|
303 | operationName = null;
|
304 | }
|
305 |
|
306 | var raw = urlData.raw !== undefined || bodyData.raw !== undefined;
|
307 |
|
308 | return { query: query, variables: variables, operationName: operationName, raw: raw };
|
309 | }
|
310 |
|
311 | /**
|
312 | * Helper function to determine if GraphiQL can be displayed.
|
313 | */
|
314 | function canDisplayGraphiQL(request, params) {
|
315 | // If `raw` exists, GraphiQL mode is not enabled.
|
316 | // Allowed to show GraphiQL if not requested as raw and this request
|
317 | // prefers HTML over JSON.
|
318 | return !params.raw && (0, _accepts2.default)(request).types(['json', 'html']) === 'html';
|
319 | }
|
320 |
|
321 | /**
|
322 | * Helper function for sending a response using only the core Node server APIs.
|
323 | */
|
324 | function sendResponse(response, type, data) {
|
325 | var chunk = new Buffer(data, 'utf8');
|
326 | response.setHeader('Content-Type', type + '; charset=utf-8');
|
327 | response.setHeader('Content-Length', String(chunk.length));
|
328 | response.end(chunk);
|
329 | } |
\ | No newline at end of file |