From fbf8a54b6d8a64644a8ec963ed47f78599a1c47a Mon Sep 17 00:00:00 2001 From: jdecroock Date: Fri, 8 Nov 2024 09:53:10 +0100 Subject: [PATCH] Address empty selection-set --- .../__tests__/ScalarLeafsRule-test.ts | 42 ++++++++++++++++++- src/validation/rules/ScalarLeafsRule.ts | 9 ++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/validation/__tests__/ScalarLeafsRule-test.ts b/src/validation/__tests__/ScalarLeafsRule-test.ts index b10cf01e18..df9546e8ee 100644 --- a/src/validation/__tests__/ScalarLeafsRule-test.ts +++ b/src/validation/__tests__/ScalarLeafsRule-test.ts @@ -1,8 +1,15 @@ import { describe, it } from 'mocha'; +import { expectJSON } from '../../__testUtils__/expectJSON'; + +import type { DocumentNode } from '../../language/ast'; +import { OperationTypeNode } from '../../language/ast'; +import { Kind } from '../../language/kinds'; + import { ScalarLeafsRule } from '../rules/ScalarLeafsRule'; +import { validate } from '../validate'; -import { expectValidationErrors } from './harness'; +import { expectValidationErrors, testSchema } from './harness'; function expectErrors(queryStr: string) { return expectValidationErrors(ScalarLeafsRule, queryStr); @@ -126,4 +133,37 @@ describe('Validate: Scalar leafs', () => { }, ]); }); + + it('object type having only one selection', () => { + const doc: DocumentNode = { + kind: Kind.DOCUMENT, + definitions: [ + { + kind: Kind.OPERATION_DEFINITION, + operation: OperationTypeNode.QUERY, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'human' }, + selectionSet: { kind: Kind.SELECTION_SET, selections: [] }, + }, + ], + }, + }, + ], + }; + + // We can't leverage expectErrors since it doesn't support passing in the + // documentNode directly. We have to do this because this is technically + // an invalid document. + const errors = validate(testSchema, doc, [ScalarLeafsRule]); + expectJSON(errors).toDeepEqual([ + { + message: + 'Field "human" of type "Human" must have at least one field selected.', + }, + ]); + }); }); diff --git a/src/validation/rules/ScalarLeafsRule.ts b/src/validation/rules/ScalarLeafsRule.ts index fb573d47e0..966143c58b 100644 --- a/src/validation/rules/ScalarLeafsRule.ts +++ b/src/validation/rules/ScalarLeafsRule.ts @@ -41,6 +41,15 @@ export function ScalarLeafsRule(context: ValidationContext): ASTVisitor { { nodes: node }, ), ); + } else if (selectionSet.selections.length === 0) { + const fieldName = node.name.value; + const typeStr = inspect(type); + context.reportError( + new GraphQLError( + `Field "${fieldName}" of type "${typeStr}" must have at least one field selected.`, + { nodes: node }, + ), + ); } } },