"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processGraphQLRequest = exports.APQ_CACHE_PREFIX = void 0;
const utils_createhash_1 = require("@apollo/utils.createhash");
const graphql_1 = require("graphql");
const schemaInstrumentation_js_1 = require("./utils/schemaInstrumentation.js");
const internalErrorClasses_js_1 = require("./internalErrorClasses.js");
const errorNormalize_js_1 = require("./errorNormalize.js");
const invokeHooks_js_1 = require("./utils/invokeHooks.js");
const makeGatewayGraphQLRequestContext_js_1 = require("./utils/makeGatewayGraphQLRequestContext.js");
const runHttpQuery_js_1 = require("./runHttpQuery.js");
const isDefined_js_1 = require("./utils/isDefined.js");
const incrementalDeliveryPolyfill_js_1 = require("./incrementalDeliveryPolyfill.js");
const HeaderMap_js_1 = require("./utils/HeaderMap.js");
exports.APQ_CACHE_PREFIX = 'apq:';
function computeQueryHash(query) {
    return (0, utils_createhash_1.createHash)('sha256').update(query).digest('hex');
}
function isBadUserInputGraphQLError(error) {
    return (error.nodes?.length === 1 &&
        error.nodes[0].kind === graphql_1.Kind.VARIABLE_DEFINITION &&
        (error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" got invalid value `) ||
            error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" of required type `) ||
            error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" of non-null type `)));
}
async function processGraphQLRequest(schemaDerivedData, server, internals, requestContext) {
    const requestListeners = (await Promise.all(internals.plugins.map((p) => p.requestDidStart?.(requestContext)))).filter(isDefined_js_1.isDefined);
    const request = requestContext.request;
    let { query, extensions } = request;
    let queryHash;
    requestContext.metrics.persistedQueryHit = false;
    requestContext.metrics.persistedQueryRegister = false;
    if (extensions?.persistedQuery) {
        if (!internals.persistedQueries) {
            return await sendErrorResponse([new internalErrorClasses_js_1.PersistedQueryNotSupportedError()]);
        }
        else if (extensions.persistedQuery.version !== 1) {
            return await sendErrorResponse([
                new graphql_1.GraphQLError('Unsupported persisted query version', {
                    extensions: { http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(400) },
                }),
            ]);
        }
        queryHash = extensions.persistedQuery.sha256Hash;
        if (query === undefined) {
            query = await internals.persistedQueries.cache.get(queryHash);
            if (query) {
                requestContext.metrics.persistedQueryHit = true;
            }
            else {
                return await sendErrorResponse([new internalErrorClasses_js_1.PersistedQueryNotFoundError()]);
            }
        }
        else {
            const computedQueryHash = computeQueryHash(query);
            if (queryHash !== computedQueryHash) {
                return await sendErrorResponse([
                    new graphql_1.GraphQLError('provided sha does not match query', {
                        extensions: { http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(400) },
                    }),
                ]);
            }
            requestContext.metrics.persistedQueryRegister = true;
        }
    }
    else if (query) {
        queryHash = computeQueryHash(query);
    }
    else {
        return await sendErrorResponse([
            new internalErrorClasses_js_1.BadRequestError('GraphQL operations must contain a non-empty `query` or a `persistedQuery` extension.'),
        ]);
    }
    requestContext.queryHash = queryHash;
    requestContext.source = query;
    await Promise.all(requestListeners.map((l) => l.didResolveSource?.(requestContext)));
    if (schemaDerivedData.documentStore) {
        try {
            requestContext.document = await schemaDerivedData.documentStore.get(schemaDerivedData.documentStoreKeyPrefix + queryHash);
        }
        catch (err) {
            server.logger.warn('An error occurred while attempting to read from the documentStore. ' +
                (0, errorNormalize_js_1.ensureError)(err).message);
        }
    }
    if (!requestContext.document) {
        const parsingDidEnd = await (0, invokeHooks_js_1.invokeDidStartHook)(requestListeners, async (l) => l.parsingDidStart?.(requestContext));
        try {
            requestContext.document = (0, graphql_1.parse)(query, internals.parseOptions);
        }
        catch (syntaxMaybeError) {
            const error = (0, errorNormalize_js_1.ensureError)(syntaxMaybeError);
            await parsingDidEnd(error);
            return await sendErrorResponse([
                new internalErrorClasses_js_1.SyntaxError((0, errorNormalize_js_1.ensureGraphQLError)(error)),
            ]);
        }
        await parsingDidEnd();
        if (internals.dangerouslyDisableValidation !== true) {
            const validationDidEnd = await (0, invokeHooks_js_1.invokeDidStartHook)(requestListeners, async (l) => l.validationDidStart?.(requestContext));
            const validationErrors = (0, graphql_1.validate)(schemaDerivedData.schema, requestContext.document, [...graphql_1.specifiedRules, ...internals.validationRules]);
            if (validationErrors.length === 0) {
                await validationDidEnd();
            }
            else {
                await validationDidEnd(validationErrors);
                return await sendErrorResponse(validationErrors.map((error) => new internalErrorClasses_js_1.ValidationError(error)));
            }
        }
        if (schemaDerivedData.documentStore) {
            Promise.resolve(schemaDerivedData.documentStore.set(schemaDerivedData.documentStoreKeyPrefix + queryHash, requestContext.document)).catch((err) => server.logger.warn('Could not store validated document. ' + err?.message || err));
        }
    }
    const operation = (0, graphql_1.getOperationAST)(requestContext.document, request.operationName);
    requestContext.operation = operation || undefined;
    requestContext.operationName = operation?.name?.value || null;
    if (request.http?.method === 'GET' &&
        operation?.operation &&
        operation.operation !== 'query') {
        return await sendErrorResponse([
            new internalErrorClasses_js_1.BadRequestError(`GET requests only support query operations, not ${operation.operation} operations`, {
                extensions: {
                    http: { status: 405, headers: new HeaderMap_js_1.HeaderMap([['allow', 'POST']]) },
                },
            }),
        ]);
    }
    try {
        await Promise.all(requestListeners.map((l) => l.didResolveOperation?.(requestContext)));
    }
    catch (err) {
        return await sendErrorResponse([(0, errorNormalize_js_1.ensureGraphQLError)(err)]);
    }
    if (requestContext.metrics.persistedQueryRegister &&
        internals.persistedQueries) {
        const ttl = internals.persistedQueries?.ttl;
        Promise.resolve(internals.persistedQueries.cache.set(queryHash, query, ttl !== undefined
            ? { ttl: internals.persistedQueries?.ttl }
            : undefined)).catch(server.logger.warn);
    }
    const responseFromPlugin = await (0, invokeHooks_js_1.invokeHooksUntilDefinedAndNonNull)(requestListeners, async (l) => await l.responseForOperation?.(requestContext));
    if (responseFromPlugin !== null) {
        requestContext.response.body = responseFromPlugin.body;
        (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, responseFromPlugin.http);
    }
    else {
        const executionListeners = (await Promise.all(requestListeners.map((l) => l.executionDidStart?.(requestContext)))).filter(isDefined_js_1.isDefined);
        executionListeners.reverse();
        if (executionListeners.some((l) => l.willResolveField)) {
            const invokeWillResolveField = (...args) => (0, invokeHooks_js_1.invokeSyncDidStartHook)(executionListeners, (l) => l.willResolveField?.(...args));
            Object.defineProperty(requestContext.contextValue, schemaInstrumentation_js_1.symbolExecutionDispatcherWillResolveField, { value: invokeWillResolveField });
            if (internals.fieldResolver) {
                Object.defineProperty(requestContext.contextValue, schemaInstrumentation_js_1.symbolUserFieldResolver, {
                    value: internals.fieldResolver,
                });
            }
            (0, schemaInstrumentation_js_1.enablePluginsForSchemaResolvers)(schemaDerivedData.schema);
        }
        try {
            const fullResult = await execute(requestContext);
            const result = 'singleResult' in fullResult
                ? fullResult.singleResult
                : fullResult.initialResult;
            if (!requestContext.operation) {
                if (!result.errors?.length) {
                    throw new Error('Unexpected error: Apollo Server did not resolve an operation but execute did not return errors');
                }
                throw new internalErrorClasses_js_1.OperationResolutionError(result.errors[0]);
            }
            const resultErrors = result.errors?.map((e) => {
                if (isBadUserInputGraphQLError(e) && e.extensions?.code == null) {
                    return new internalErrorClasses_js_1.UserInputError(e);
                }
                return e;
            });
            if (resultErrors) {
                await didEncounterErrors(resultErrors);
            }
            const { formattedErrors, httpFromErrors } = resultErrors
                ? formatErrors(resultErrors)
                : { formattedErrors: undefined, httpFromErrors: (0, runHttpQuery_js_1.newHTTPGraphQLHead)() };
            if (internals.status400ForVariableCoercionErrors &&
                resultErrors?.length &&
                result.data === undefined &&
                !httpFromErrors.status) {
                httpFromErrors.status = 400;
            }
            (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, httpFromErrors);
            if ('singleResult' in fullResult) {
                requestContext.response.body = {
                    kind: 'single',
                    singleResult: {
                        ...result,
                        errors: formattedErrors,
                    },
                };
            }
            else {
                requestContext.response.body = {
                    kind: 'incremental',
                    initialResult: {
                        ...fullResult.initialResult,
                        errors: formattedErrors,
                    },
                    subsequentResults: fullResult.subsequentResults,
                };
            }
        }
        catch (executionMaybeError) {
            const executionError = (0, errorNormalize_js_1.ensureError)(executionMaybeError);
            await Promise.all(executionListeners.map((l) => l.executionDidEnd?.(executionError)));
            return await sendErrorResponse([(0, errorNormalize_js_1.ensureGraphQLError)(executionError)]);
        }
        await Promise.all(executionListeners.map((l) => l.executionDidEnd?.()));
    }
    await invokeWillSendResponse();
    if (!requestContext.response.body) {
        throw Error('got to end of processGraphQLRequest without setting body?');
    }
    return requestContext.response;
    async function execute(requestContext) {
        const { request, document } = requestContext;
        if (internals.__testing_incrementalExecutionResults) {
            return internals.__testing_incrementalExecutionResults;
        }
        else if (internals.gatewayExecutor) {
            const result = await internals.gatewayExecutor((0, makeGatewayGraphQLRequestContext_js_1.makeGatewayGraphQLRequestContext)(requestContext, server, internals));
            return { singleResult: result };
        }
        else {
            const resultOrResults = await (0, incrementalDeliveryPolyfill_js_1.executeIncrementally)({
                schema: schemaDerivedData.schema,
                document,
                rootValue: typeof internals.rootValue === 'function'
                    ? internals.rootValue(document)
                    : internals.rootValue,
                contextValue: requestContext.contextValue,
                variableValues: request.variables,
                operationName: request.operationName,
                fieldResolver: internals.fieldResolver,
            });
            if ('initialResult' in resultOrResults) {
                return {
                    initialResult: resultOrResults.initialResult,
                    subsequentResults: formatErrorsInSubsequentResults(resultOrResults.subsequentResults),
                };
            }
            else {
                return { singleResult: resultOrResults };
            }
        }
    }
    async function* formatErrorsInSubsequentResults(results) {
        for await (const result of results) {
            const payload = result.incremental
                ? {
                    ...result,
                    incremental: await seriesAsyncMap(result.incremental, async (incrementalResult) => {
                        const { errors } = incrementalResult;
                        if (errors) {
                            await Promise.all(requestListeners.map((l) => l.didEncounterSubsequentErrors?.(requestContext, errors)));
                            return {
                                ...incrementalResult,
                                errors: formatErrors(errors).formattedErrors,
                            };
                        }
                        return incrementalResult;
                    }),
                }
                : result;
            await Promise.all(requestListeners.map((l) => l.willSendSubsequentPayload?.(requestContext, payload)));
            yield payload;
        }
    }
    async function invokeWillSendResponse() {
        await Promise.all(requestListeners.map((l) => l.willSendResponse?.(requestContext)));
    }
    async function didEncounterErrors(errors) {
        requestContext.errors = errors;
        return await Promise.all(requestListeners.map((l) => l.didEncounterErrors?.(requestContext)));
    }
    async function sendErrorResponse(errors) {
        await didEncounterErrors(errors);
        const { formattedErrors, httpFromErrors } = formatErrors(errors);
        requestContext.response.body = {
            kind: 'single',
            singleResult: {
                errors: formattedErrors,
            },
        };
        (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, httpFromErrors);
        if (!requestContext.response.http.status) {
            requestContext.response.http.status = 500;
        }
        await invokeWillSendResponse();
        return requestContext.response;
    }
    function formatErrors(errors) {
        return (0, errorNormalize_js_1.normalizeAndFormatErrors)(errors, {
            formatError: internals.formatError,
            includeStacktraceInErrorResponses: internals.includeStacktraceInErrorResponses,
        });
    }
}
exports.processGraphQLRequest = processGraphQLRequest;
async function seriesAsyncMap(ts, fn) {
    const us = [];
    for (const t of ts) {
        const u = await fn(t);
        us.push(u);
    }
    return us;
}
//# sourceMappingURL=requestPipeline.js.map