"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