forked from parse-community/parse-server
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathqueryComplexity.js
More file actions
124 lines (112 loc) · 4.01 KB
/
queryComplexity.js
File metadata and controls
124 lines (112 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { GraphQLError } from 'graphql';
import logger from '../../logger';
function calculateQueryComplexity(operation, fragments, limits = {}) {
let maxDepth = 0;
let totalFields = 0;
const fragmentCache = new Map();
const { maxDepth: allowedMaxDepth, maxFields: allowedMaxFields } = limits;
function visitSelectionSet(selectionSet, depth, visitedFragments) {
if (!selectionSet) {
return;
}
if (
(allowedMaxFields !== undefined && allowedMaxFields !== -1 && totalFields > allowedMaxFields) ||
(allowedMaxDepth !== undefined && allowedMaxDepth !== -1 && maxDepth > allowedMaxDepth)
) {
return;
}
for (const selection of selectionSet.selections) {
if (selection.kind === 'Field') {
totalFields++;
const newDepth = depth + 1;
if (newDepth > maxDepth) {
maxDepth = newDepth;
}
if (selection.selectionSet) {
visitSelectionSet(selection.selectionSet, newDepth, visitedFragments);
}
} else if (selection.kind === 'InlineFragment') {
visitSelectionSet(selection.selectionSet, depth, visitedFragments);
} else if (selection.kind === 'FragmentSpread') {
const name = selection.name.value;
if (fragmentCache.has(name)) {
const cached = fragmentCache.get(name);
totalFields += cached.fields;
const adjustedDepth = depth + cached.maxDepthDelta;
if (adjustedDepth > maxDepth) {
maxDepth = adjustedDepth;
}
continue;
}
if (visitedFragments.has(name)) {
continue;
}
const fragment = fragments[name];
if (fragment) {
visitedFragments.add(name);
const savedFields = totalFields;
const savedMaxDepth = maxDepth;
maxDepth = depth;
visitSelectionSet(fragment.selectionSet, depth, visitedFragments);
const fieldsContribution = totalFields - savedFields;
const maxDepthDelta = maxDepth - depth;
fragmentCache.set(name, { fields: fieldsContribution, maxDepthDelta });
maxDepth = Math.max(savedMaxDepth, maxDepth);
visitedFragments.delete(name);
}
}
}
}
visitSelectionSet(operation.selectionSet, 0, new Set());
return { depth: maxDepth, fields: totalFields };
}
function createComplexityValidationPlugin(getConfig) {
return {
requestDidStart: (requestContext) => ({
didResolveOperation: async () => {
const auth = requestContext.contextValue?.auth;
if (auth?.isMaster || auth?.isMaintenance) {
return;
}
const config = getConfig();
if (!config) {
return;
}
const { graphQLDepth, graphQLFields } = config;
if (graphQLDepth === -1 && graphQLFields === -1) {
return;
}
const fragments = {};
for (const definition of requestContext.document.definitions) {
if (definition.kind === 'FragmentDefinition') {
fragments[definition.name.value] = definition;
}
}
const { depth, fields } = calculateQueryComplexity(
requestContext.operation,
fragments,
{ maxDepth: graphQLDepth, maxFields: graphQLFields }
);
if (graphQLDepth !== -1 && depth > graphQLDepth) {
const message = `GraphQL query depth of ${depth} exceeds maximum allowed depth of ${graphQLDepth}`;
logger.warn(message);
throw new GraphQLError(message, {
extensions: {
http: { status: 400 },
},
});
}
if (graphQLFields !== -1 && fields > graphQLFields) {
const message = `Number of GraphQL fields (${fields}) exceeds maximum allowed (${graphQLFields})`;
logger.warn(message);
throw new GraphQLError(message, {
extensions: {
http: { status: 400 },
},
});
}
},
}),
};
}
export { calculateQueryComplexity, createComplexityValidationPlugin };