Hello community,
On my Apollo Server v3, I have a plugin that validate queries to reject those with too much fields queried for each node. The logic happens in the validationDidStart
. I throw an ValidationError
that is well formatted and returned to the Client.
But since AS v4, the errors thrown in validationDidStart
are not formatted anymore and returns Internal Server Error
everytime.
Unfortunately this check requires the AST Document
but it should happen before validating the fields existence because I don’t want the server to validate thousands of fields.
Do you know how I could keep my ValidationError
?
Thank you
Here is the plugin code:
Field Limit Plugin Code
/** Validate queries to reject those with too much fields queried for each node
* @param {number} limit - limit of fields that can be queried inside a single node before throwing an error
*/
const fieldLimitPlugin = (limit: number): ApolloServerPlugin => ({
async requestDidStart() {
return {
async validationDidStart(validationRequestContext) {
const {
document: { definitions },
} = validationRequestContext;
// List fragment definitions before validation to refer to them in field count
const fragments: Record<string, FragmentDefinitionNode> = {};
definitions.forEach((node) => {
if (node.kind === 'FragmentDefinition') {
fragments[node.name.value] = node;
}
});
// Recursively counts fields by taking into account nested fragments
const countFields = (nodes: readonly SelectionNode[]) =>
nodes.reduce((acc, curr) => {
switch (curr.kind) {
case 'Field':
acc++;
break;
case 'InlineFragment':
acc += countFields(curr.selectionSet.selections);
break;
case 'FragmentSpread':
acc += countFields(
fragments[curr.name.value].selectionSet.selections,
);
break;
default:
break;
}
return acc;
}, 0);
// Recursively checks if a node has too much fields queried inside it
const hasInvalidNode = (
node: ExecutableDefinitionNode | SelectionNode | DefinitionNode,
): boolean => {
if (!('selectionSet' in node) || !node.selectionSet) return false;
const {
selectionSet: { selections },
} = node;
// count fields # including nested fragments
if (countFields(selections) > limit) return true;
return selections.some(hasInvalidNode);
};
// Checks query definitions to validate if a field goes over the defined limit of fields
if (definitions.some(hasInvalidNode))
throw new ValidationError('Query field limit reached');
},
};
},
});