Apollo Server plugin validationDidStart throwing internal error

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 :slight_smile:

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');
      },
    };
  },
});