244 lines
11 KiB
JavaScript
244 lines
11 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.mergeHTTPGraphQLHead = exports.newHTTPGraphQLHead = exports.prettyJSONStringify = exports.runHttpQuery = void 0;
|
|
const ApolloServer_js_1 = require("./ApolloServer.js");
|
|
const graphql_1 = require("graphql");
|
|
const internalErrorClasses_js_1 = require("./internalErrorClasses.js");
|
|
const negotiator_1 = __importDefault(require("negotiator"));
|
|
const HeaderMap_js_1 = require("./utils/HeaderMap.js");
|
|
function fieldIfString(o, fieldName) {
|
|
const value = o[fieldName];
|
|
if (typeof value === 'string') {
|
|
return value;
|
|
}
|
|
return undefined;
|
|
}
|
|
function searchParamIfSpecifiedOnce(searchParams, paramName) {
|
|
const values = searchParams.getAll(paramName);
|
|
switch (values.length) {
|
|
case 0:
|
|
return undefined;
|
|
case 1:
|
|
return values[0];
|
|
default:
|
|
throw new internalErrorClasses_js_1.BadRequestError(`The '${paramName}' search parameter may only be specified once.`);
|
|
}
|
|
}
|
|
function jsonParsedSearchParamIfSpecifiedOnce(searchParams, fieldName) {
|
|
const value = searchParamIfSpecifiedOnce(searchParams, fieldName);
|
|
if (value === undefined) {
|
|
return undefined;
|
|
}
|
|
let hopefullyRecord;
|
|
try {
|
|
hopefullyRecord = JSON.parse(value);
|
|
}
|
|
catch {
|
|
throw new internalErrorClasses_js_1.BadRequestError(`The ${fieldName} search parameter contains invalid JSON.`);
|
|
}
|
|
if (!isStringRecord(hopefullyRecord)) {
|
|
throw new internalErrorClasses_js_1.BadRequestError(`The ${fieldName} search parameter should contain a JSON-encoded object.`);
|
|
}
|
|
return hopefullyRecord;
|
|
}
|
|
function fieldIfRecord(o, fieldName) {
|
|
const value = o[fieldName];
|
|
if (isStringRecord(value)) {
|
|
return value;
|
|
}
|
|
return undefined;
|
|
}
|
|
function isStringRecord(o) {
|
|
return (!!o && typeof o === 'object' && !Buffer.isBuffer(o) && !Array.isArray(o));
|
|
}
|
|
function isNonEmptyStringRecord(o) {
|
|
return isStringRecord(o) && Object.keys(o).length > 0;
|
|
}
|
|
function ensureQueryIsStringOrMissing(query) {
|
|
if (!query || typeof query === 'string') {
|
|
return;
|
|
}
|
|
if (query.kind === graphql_1.Kind.DOCUMENT) {
|
|
throw new internalErrorClasses_js_1.BadRequestError("GraphQL queries must be strings. It looks like you're sending the " +
|
|
'internal graphql-js representation of a parsed query in your ' +
|
|
'request instead of a request in the GraphQL query language. You ' +
|
|
'can convert an AST to a string using the `print` function from ' +
|
|
'`graphql`, or use a client like `apollo-client` which converts ' +
|
|
'the internal representation to a string for you.');
|
|
}
|
|
else {
|
|
throw new internalErrorClasses_js_1.BadRequestError('GraphQL queries must be strings.');
|
|
}
|
|
}
|
|
async function runHttpQuery({ server, httpRequest, contextValue, schemaDerivedData, internals, sharedResponseHTTPGraphQLHead, }) {
|
|
let graphQLRequest;
|
|
switch (httpRequest.method) {
|
|
case 'POST': {
|
|
if (!isNonEmptyStringRecord(httpRequest.body)) {
|
|
throw new internalErrorClasses_js_1.BadRequestError('POST body missing, invalid Content-Type, or JSON object has no keys.');
|
|
}
|
|
ensureQueryIsStringOrMissing(httpRequest.body.query);
|
|
if (typeof httpRequest.body.variables === 'string') {
|
|
throw new internalErrorClasses_js_1.BadRequestError('`variables` in a POST body should be provided as an object, not a recursively JSON-encoded string.');
|
|
}
|
|
if (typeof httpRequest.body.extensions === 'string') {
|
|
throw new internalErrorClasses_js_1.BadRequestError('`extensions` in a POST body should be provided as an object, not a recursively JSON-encoded string.');
|
|
}
|
|
if ('extensions' in httpRequest.body &&
|
|
httpRequest.body.extensions !== null &&
|
|
!isStringRecord(httpRequest.body.extensions)) {
|
|
throw new internalErrorClasses_js_1.BadRequestError('`extensions` in a POST body must be an object if provided.');
|
|
}
|
|
if ('variables' in httpRequest.body &&
|
|
httpRequest.body.variables !== null &&
|
|
!isStringRecord(httpRequest.body.variables)) {
|
|
throw new internalErrorClasses_js_1.BadRequestError('`variables` in a POST body must be an object if provided.');
|
|
}
|
|
if ('operationName' in httpRequest.body &&
|
|
httpRequest.body.operationName !== null &&
|
|
typeof httpRequest.body.operationName !== 'string') {
|
|
throw new internalErrorClasses_js_1.BadRequestError('`operationName` in a POST body must be a string if provided.');
|
|
}
|
|
graphQLRequest = {
|
|
query: fieldIfString(httpRequest.body, 'query'),
|
|
operationName: fieldIfString(httpRequest.body, 'operationName'),
|
|
variables: fieldIfRecord(httpRequest.body, 'variables'),
|
|
extensions: fieldIfRecord(httpRequest.body, 'extensions'),
|
|
http: httpRequest,
|
|
};
|
|
break;
|
|
}
|
|
case 'GET': {
|
|
const searchParams = new URLSearchParams(httpRequest.search);
|
|
graphQLRequest = {
|
|
query: searchParamIfSpecifiedOnce(searchParams, 'query'),
|
|
operationName: searchParamIfSpecifiedOnce(searchParams, 'operationName'),
|
|
variables: jsonParsedSearchParamIfSpecifiedOnce(searchParams, 'variables'),
|
|
extensions: jsonParsedSearchParamIfSpecifiedOnce(searchParams, 'extensions'),
|
|
http: httpRequest,
|
|
};
|
|
break;
|
|
}
|
|
default:
|
|
throw new internalErrorClasses_js_1.BadRequestError('Apollo Server supports only GET/POST requests.', {
|
|
extensions: {
|
|
http: {
|
|
status: 405,
|
|
headers: new HeaderMap_js_1.HeaderMap([['allow', 'GET, POST']]),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
const graphQLResponse = await (0, ApolloServer_js_1.internalExecuteOperation)({
|
|
server,
|
|
graphQLRequest,
|
|
internals,
|
|
schemaDerivedData,
|
|
sharedResponseHTTPGraphQLHead,
|
|
}, { contextValue });
|
|
if (graphQLResponse.body.kind === 'single') {
|
|
if (!graphQLResponse.http.headers.get('content-type')) {
|
|
const contentType = (0, ApolloServer_js_1.chooseContentTypeForSingleResultResponse)(httpRequest);
|
|
if (contentType === null) {
|
|
throw new internalErrorClasses_js_1.BadRequestError(`An 'accept' header was provided for this request which does not accept ` +
|
|
`${ApolloServer_js_1.MEDIA_TYPES.APPLICATION_JSON} or ${ApolloServer_js_1.MEDIA_TYPES.APPLICATION_GRAPHQL_RESPONSE_JSON}`, { extensions: { http: { status: 406 } } });
|
|
}
|
|
graphQLResponse.http.headers.set('content-type', contentType);
|
|
}
|
|
return {
|
|
...graphQLResponse.http,
|
|
body: {
|
|
kind: 'complete',
|
|
string: await internals.stringifyResult(orderExecutionResultFields(graphQLResponse.body.singleResult)),
|
|
},
|
|
};
|
|
}
|
|
const acceptHeader = httpRequest.headers.get('accept');
|
|
if (!(acceptHeader &&
|
|
new negotiator_1.default({
|
|
headers: { accept: httpRequest.headers.get('accept') },
|
|
}).mediaType([
|
|
ApolloServer_js_1.MEDIA_TYPES.MULTIPART_MIXED_NO_DEFER_SPEC,
|
|
ApolloServer_js_1.MEDIA_TYPES.MULTIPART_MIXED_EXPERIMENTAL,
|
|
]) === ApolloServer_js_1.MEDIA_TYPES.MULTIPART_MIXED_EXPERIMENTAL)) {
|
|
throw new internalErrorClasses_js_1.BadRequestError('Apollo server received an operation that uses incremental delivery ' +
|
|
'(@defer or @stream), but the client does not accept multipart/mixed ' +
|
|
'HTTP responses. To enable incremental delivery support, add the HTTP ' +
|
|
"header 'Accept: multipart/mixed; deferSpec=20220824'.", { extensions: { http: { status: 406 } } });
|
|
}
|
|
graphQLResponse.http.headers.set('content-type', 'multipart/mixed; boundary="-"; deferSpec=20220824');
|
|
return {
|
|
...graphQLResponse.http,
|
|
body: {
|
|
kind: 'chunked',
|
|
asyncIterator: writeMultipartBody(graphQLResponse.body.initialResult, graphQLResponse.body.subsequentResults),
|
|
},
|
|
};
|
|
}
|
|
exports.runHttpQuery = runHttpQuery;
|
|
async function* writeMultipartBody(initialResult, subsequentResults) {
|
|
yield `\r\n---\r\ncontent-type: application/json; charset=utf-8\r\n\r\n${JSON.stringify(orderInitialIncrementalExecutionResultFields(initialResult))}\r\n---${initialResult.hasNext ? '' : '--'}\r\n`;
|
|
for await (const result of subsequentResults) {
|
|
yield `content-type: application/json; charset=utf-8\r\n\r\n${JSON.stringify(orderSubsequentIncrementalExecutionResultFields(result))}\r\n---${result.hasNext ? '' : '--'}\r\n`;
|
|
}
|
|
}
|
|
function orderExecutionResultFields(result) {
|
|
return {
|
|
errors: result.errors,
|
|
data: result.data,
|
|
extensions: result.extensions,
|
|
};
|
|
}
|
|
function orderInitialIncrementalExecutionResultFields(result) {
|
|
return {
|
|
hasNext: result.hasNext,
|
|
errors: result.errors,
|
|
data: result.data,
|
|
incremental: orderIncrementalResultFields(result.incremental),
|
|
extensions: result.extensions,
|
|
};
|
|
}
|
|
function orderSubsequentIncrementalExecutionResultFields(result) {
|
|
return {
|
|
hasNext: result.hasNext,
|
|
incremental: orderIncrementalResultFields(result.incremental),
|
|
extensions: result.extensions,
|
|
};
|
|
}
|
|
function orderIncrementalResultFields(incremental) {
|
|
return incremental?.map((i) => ({
|
|
hasNext: i.hasNext,
|
|
errors: i.errors,
|
|
path: i.path,
|
|
label: i.label,
|
|
data: i.data,
|
|
items: i.items,
|
|
extensions: i.extensions,
|
|
}));
|
|
}
|
|
function prettyJSONStringify(value) {
|
|
return JSON.stringify(value) + '\n';
|
|
}
|
|
exports.prettyJSONStringify = prettyJSONStringify;
|
|
function newHTTPGraphQLHead(status) {
|
|
return {
|
|
status,
|
|
headers: new HeaderMap_js_1.HeaderMap(),
|
|
};
|
|
}
|
|
exports.newHTTPGraphQLHead = newHTTPGraphQLHead;
|
|
function mergeHTTPGraphQLHead(target, source) {
|
|
if (source.status) {
|
|
target.status = source.status;
|
|
}
|
|
if (source.headers) {
|
|
for (const [name, value] of source.headers) {
|
|
target.headers.set(name, value);
|
|
}
|
|
}
|
|
}
|
|
exports.mergeHTTPGraphQLHead = mergeHTTPGraphQLHead;
|
|
//# sourceMappingURL=runHttpQuery.js.map
|