diff --git a/src/index.ts b/src/index.ts index 853a3a2ec0..85ee135fa4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -366,6 +366,7 @@ export { KnownArgumentNamesRule, KnownDirectivesRule, KnownFragmentNamesRule, + KnownOperationTypesRule, KnownTypeNamesRule, LoneAnonymousOperationRule, NoFragmentCyclesRule, diff --git a/src/validation/__tests__/KnownOperationTypesRules-test.ts b/src/validation/__tests__/KnownOperationTypesRules-test.ts new file mode 100644 index 0000000000..93ab581725 --- /dev/null +++ b/src/validation/__tests__/KnownOperationTypesRules-test.ts @@ -0,0 +1,60 @@ +import { describe, it } from 'mocha'; + +import { KnownOperationTypesRule } from '../rules/KnownOperationTypesRule.js'; + +import { expectValidationErrors } from './harness.js'; + +function expectErrors(queryStr: string) { + return expectValidationErrors(KnownOperationTypesRule, queryStr); +} + +function expectValid(queryStr: string) { + expectErrors(queryStr).toDeepEqual([]); +} + +describe('Validate: Known operation types', () => { + it('one known operation', () => { + expectValid(` + { field } + `); + }); + + it('unknown mutation operation', () => { + expectErrors(` + mutation { field } + `).toDeepEqual([ + { + message: 'The mutation operation is not supported by the schema.', + locations: [{ line: 2, column: 7 }], + }, + ]); + }); + + it('unknown subscription operation', () => { + expectErrors(` + subscription { field } + `).toDeepEqual([ + { + message: 'The subscription operation is not supported by the schema.', + locations: [{ line: 2, column: 7 }], + }, + ]); + }); + + it('mixture of known and unknown operations', () => { + expectErrors(` + query { field } + mutation { field } + subscription { field } + `).toDeepEqual([ + { + message: 'The mutation operation is not supported by the schema.', + locations: [{ line: 3, column: 7 }], + }, + { + message: 'The subscription operation is not supported by the schema.', + locations: [{ line: 4, column: 7 }], + }, + ]); + }); +}); diff --git a/src/validation/index.ts b/src/validation/index.ts index dbe8e57dc0..b1998b5300 100644 --- a/src/validation/index.ts +++ b/src/validation/index.ts @@ -33,6 +33,9 @@ export { KnownDirectivesRule } from './rules/KnownDirectivesRule.js'; // Spec Section: "Fragment spread target defined" export { KnownFragmentNamesRule } from './rules/KnownFragmentNamesRule.js'; +// Spec Section: "Operation Type Existence" +export { KnownOperationTypesRule } from './rules/KnownOperationTypesRule.js'; + // Spec Section: "Fragment Spread Type Existence" export { KnownTypeNamesRule } from './rules/KnownTypeNamesRule.js'; diff --git a/src/validation/rules/KnownOperationTypesRule.ts b/src/validation/rules/KnownOperationTypesRule.ts new file mode 100644 index 0000000000..4b17df05fb --- /dev/null +++ b/src/validation/rules/KnownOperationTypesRule.ts @@ -0,0 +1,32 @@ +import { GraphQLError } from '../../error/GraphQLError.js'; + +import type { ASTVisitor } from '../../language/visitor.js'; + +import type { ValidationContext } from '../ValidationContext.js'; + +/** + * Known Operation Types + * + * A GraphQL document is only valid if when it contains an operation, + * the root type for the operation exists within the schema. + * + * See https://spec.graphql.org/draft/#sec-Operation-Type-Existence + */ +export function KnownOperationTypesRule( + context: ValidationContext, +): ASTVisitor { + const schema = context.getSchema(); + return { + OperationDefinition(node) { + const operation = node.operation; + if (!schema.getRootType(operation)) { + context.reportError( + new GraphQLError( + `The ${operation} operation is not supported by the schema.`, + { nodes: node }, + ), + ); + } + }, + }; +} diff --git a/src/validation/specifiedRules.ts b/src/validation/specifiedRules.ts index 9884bea8b9..fa2d746de4 100644 --- a/src/validation/specifiedRules.ts +++ b/src/validation/specifiedRules.ts @@ -19,6 +19,8 @@ import { import { KnownDirectivesRule } from './rules/KnownDirectivesRule.js'; // Spec Section: "Fragment spread target defined" import { KnownFragmentNamesRule } from './rules/KnownFragmentNamesRule.js'; +// Spec Section: "Operation Type Existence" +import { KnownOperationTypesRule } from './rules/KnownOperationTypesRule.js'; // Spec Section: "Fragment Spread Type Existence" import { KnownTypeNamesRule } from './rules/KnownTypeNamesRule.js'; // Spec Section: "Lone Anonymous Operation" @@ -91,6 +93,7 @@ export const recommendedRules = Object.freeze([MaxIntrospectionDepthRule]); */ export const specifiedRules: ReadonlyArray = Object.freeze([ ExecutableDefinitionsRule, + KnownOperationTypesRule, UniqueOperationNamesRule, LoneAnonymousOperationRule, SingleFieldSubscriptionsRule,