graphql-yoga
Version:
210 lines (209 loc) • 7.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.withSchemaCoordinate = withSchemaCoordinate;
exports.isGraphQLError = isGraphQLError;
exports.isOriginalGraphQLError = isOriginalGraphQLError;
exports.isAbortError = isAbortError;
exports.isRequestBodyLimitError = isRequestBodyLimitError;
exports.responseFromBodyLimitError = responseFromBodyLimitError;
exports.handleError = handleError;
exports.getResponseInitByRespectingErrors = getResponseInitByRespectingErrors;
exports.areGraphQLErrors = areGraphQLErrors;
const graphql_1 = require("graphql");
const server_1 = require("@whatwg-node/server");
/**
* Attach experimental schema `coordinate` to a GraphQLError.
* graphql-js GraphQLErrorOptions does not know about coordinate yet, so this
* uses defineProperty (same approach as `@graphql-tools/utils` locatedError).
*
* Prefer reading with `getSchemaCoordinate` from `@graphql-tools/utils`.
*/
function withSchemaCoordinate(error, coordinate) {
if (coordinate != null && error.coordinate == null) {
Object.defineProperties(error, {
coordinate: { value: coordinate, enumerable: true, configurable: true },
});
}
return error;
}
function isAggregateError(obj) {
return obj != null && typeof obj === 'object' && 'errors' in obj;
}
function hasToString(obj) {
return obj != null && typeof obj.toString === 'function';
}
function isGraphQLError(val) {
return val instanceof graphql_1.GraphQLError;
}
function isOriginalGraphQLError(val) {
if (val instanceof graphql_1.GraphQLError) {
if (val.originalError != null) {
return isOriginalGraphQLError(val.originalError);
}
return true;
}
return false;
}
function isAbortError(error) {
return (typeof error === 'object' &&
error?.constructor?.name === 'DOMException' &&
(error.name === 'AbortError' ||
error.name === 'TimeoutError'));
}
function isRequestBodyLimitError(error) {
return error instanceof server_1.RequestBodyTooLargeError || error instanceof server_1.InvalidContentLengthError;
}
function headersInitToRecord(headers) {
return Object.fromEntries(new Headers(headers).entries());
}
function graphQLErrorFromBodyLimitError(error) {
const headers = headersInitToRecord(error.headers);
if (error instanceof server_1.RequestBodyTooLargeError) {
return new graphql_1.GraphQLError(error.message, {
originalError: error,
extensions: {
http: {
status: 413,
...(Object.keys(headers).length > 0 ? { headers } : {}),
},
code: 'REQUEST_ENTITY_TOO_LARGE',
},
});
}
return new graphql_1.GraphQLError(error.message, {
originalError: error,
extensions: {
http: {
status: 400,
...(Object.keys(headers).length > 0 ? { headers } : {}),
},
code: 'BAD_REQUEST',
},
});
}
// Passed as `responseFromError` to `@whatwg-node/server`'s `useLimitRequestBodySize` so the
// early (Content-Length based) rejection responds with a GraphQL-shaped error body instead of
// that plugin's plain-text default.
function responseFromBodyLimitError(error, fetchAPI) {
if (!isRequestBodyLimitError(error)) {
throw error;
}
return new fetchAPI.Response(JSON.stringify({ errors: [graphQLErrorFromBodyLimitError(error)] }), {
status: error.status,
headers: {
'content-type': 'application/json; charset=utf-8',
...error.headers,
},
});
}
function handleError(error, maskedErrorsOpts, context) {
const log = context.log;
const errors = new Set();
if (isAggregateError(error)) {
for (const singleError of error.errors) {
const handledErrors = handleError(singleError, maskedErrorsOpts, context);
for (const handledError of handledErrors) {
errors.add(handledError);
}
}
}
else if (isAbortError(error)) {
log.debug('Request aborted');
}
else if (isRequestBodyLimitError(error)) {
errors.add(graphQLErrorFromBodyLimitError(error));
}
else if (maskedErrorsOpts) {
const maskedError = maskedErrorsOpts.maskError(error, maskedErrorsOpts.errorMessage, maskedErrorsOpts.isDev, context);
errors.add(isGraphQLError(maskedError)
? maskedError
: new graphql_1.GraphQLError(maskedError.message, {
originalError: maskedError,
}));
}
else if (isGraphQLError(error)) {
errors.add(error);
}
else if (error instanceof Error) {
errors.add(new graphql_1.GraphQLError(error.message, {
originalError: error,
}));
}
else if (typeof error === 'string') {
errors.add(new graphql_1.GraphQLError(error, {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
unexpected: true,
},
}));
}
else if (hasToString(error)) {
errors.add(new graphql_1.GraphQLError(error.toString(), {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
unexpected: true,
},
}));
}
else {
log.error({ err: error });
errors.add(new graphql_1.GraphQLError('Unexpected error.', {
extensions: {
unexpected: true,
},
}));
}
return Array.from(errors);
}
function getResponseInitByRespectingErrors(result, headers = {}, isApplicationJson = false) {
let status;
let unexpectedErrorExists = false;
if ('extensions' in result && result.extensions?.http) {
if (result.extensions.http.headers) {
Object.assign(headers, result.extensions.http.headers);
}
if (result.extensions.http.status) {
status = result.extensions.http.status;
}
}
if ('errors' in result && result.errors?.length) {
for (const error of result.errors) {
if (error.extensions?.['http']) {
if (error.extensions['http'].headers) {
Object.assign(headers, error.extensions['http'].headers);
}
if (isApplicationJson && error.extensions['http'].spec) {
continue;
}
if (error.extensions['http'].status &&
(!status || error.extensions['http'].status > status)) {
status = error.extensions['http'].status;
}
}
else if (!isOriginalGraphQLError(error) || error.extensions?.['unexpected']) {
unexpectedErrorExists = true;
}
}
}
else {
status ||= 200;
}
if (!status) {
if (unexpectedErrorExists && !('data' in result)) {
status = 500;
}
else {
status = 200;
}
}
return {
status,
headers,
};
}
function areGraphQLErrors(obj) {
return (Array.isArray(obj) &&
obj.length > 0 &&
// if one item in the array is a GraphQLError, we're good
obj.some(isGraphQLError));
}