@graphql-inspector/core
Version:
Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.
102 lines (101 loc) • 3.45 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateQueryDepth = validateQueryDepth;
exports.calculateDepth = calculateDepth;
exports.countDepth = countDepth;
const graphql_1 = require("graphql");
function validateQueryDepth({ source, doc, maxDepth, fragmentGraph, }) {
try {
calculateDepth({
node: doc,
currentDepth: 0,
maxDepth,
getFragment(name) {
return fragmentGraph.getNodeData(name);
},
});
}
catch (errorOrNode) {
if (errorOrNode instanceof Error) {
throw errorOrNode;
}
const node = errorOrNode;
return new graphql_1.GraphQLError(`Query exceeds maximum depth of ${maxDepth}`, node, source, node.loc?.start ? [node.loc.start] : undefined);
}
}
function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
if (maxDepth && currentDepth > maxDepth) {
throw node;
}
switch (node.kind) {
case graphql_1.Kind.FIELD: {
if (node.name.value.startsWith('__') || !node.selectionSet) {
return 0;
}
const maxInnerDepth = calculateDepth({
node: node.selectionSet,
currentDepth: currentDepth + 1,
maxDepth,
getFragment,
});
return 1 + maxInnerDepth;
}
case graphql_1.Kind.SELECTION_SET: {
return Math.max(...node.selections.map(selection => {
return calculateDepth({
node: selection,
currentDepth,
maxDepth,
getFragment,
});
}));
}
case graphql_1.Kind.DOCUMENT: {
return Math.max(...node.definitions.map(def => {
return calculateDepth({
node: def,
currentDepth,
maxDepth,
getFragment,
});
}));
}
case graphql_1.Kind.OPERATION_DEFINITION:
case graphql_1.Kind.INLINE_FRAGMENT:
case graphql_1.Kind.FRAGMENT_DEFINITION: {
return Math.max(...node.selectionSet.selections.map(selection => {
return calculateDepth({
node: selection,
currentDepth,
maxDepth,
getFragment,
});
}));
}
case graphql_1.Kind.FRAGMENT_SPREAD:
return calculateDepth({
node: getFragment(node.name.value),
currentDepth,
maxDepth,
getFragment,
});
default: {
throw new Error(`Couldn't handle ${node.kind}`);
}
}
}
function countDepth(node, parentDepth, getFragmentReference) {
let depth = parentDepth;
if ('selectionSet' in node && node.selectionSet) {
for (const child of node.selectionSet.selections) {
depth = Math.max(depth, countDepth(child, parentDepth + 1, getFragmentReference));
}
}
if (node.kind === graphql_1.Kind.FRAGMENT_SPREAD) {
const fragment = getFragmentReference(node.name.value);
if (fragment) {
depth = Math.max(depth, countDepth(fragment, parentDepth + 1, getFragmentReference));
}
}
return depth;
}
;