diff --git a/src/type/__tests__/validation-test.js b/src/type/__tests__/validation-test.js index 7b914c5fa1..8a1947dd46 100644 --- a/src/type/__tests__/validation-test.js +++ b/src/type/__tests__/validation-test.js @@ -713,7 +713,7 @@ describe('Type System: Input Objects must have fields', () => { schema = extendSchema( schema, parse(` - directive @test on ENUM + directive @test on INPUT_OBJECT extend input SomeInputObject @test `), diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index b65b8a4d79..224efc73c6 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -787,6 +787,25 @@ describe('Schema Builder', () => { const errors = validateSchema(schema); expect(errors.length).to.equal(0); }); + + it('Rejects invalid SDL', () => { + const doc = parse(` + type Query { + foo: String @unknown + } + `); + expect(() => buildASTSchema(doc)).to.throw('Unknown directive "unknown".'); + }); + + it('Allows to disable SDL validation', () => { + const body = ` + type Query { + foo: String @unknown + } + `; + buildSchema(body, { assumeValid: true }); + buildSchema(body, { assumeValidSDL: true }); + }); }); describe('Failures', () => { diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index 910cc80b48..1fcde93745 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -1018,6 +1018,23 @@ describe('extendSchema', () => { expect(isScalarType(arg1.type)).to.equal(true); }); + it('Rejects invalid SDL', () => { + const sdl = ` + extend schema @unknown + `; + expect(() => extendTestSchema(sdl)).to.throw( + 'Unknown directive "unknown".', + ); + }); + + it('Allows to disable SDL validation', () => { + const sdl = ` + extend schema @unknown + `; + extendTestSchema(sdl, { assumeValid: true }); + extendTestSchema(sdl, { assumeValidSDL: true }); + }); + it('does not allow replacing a default directive', () => { const sdl = ` directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index d9bbb0241b..030d5c0648 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -11,6 +11,7 @@ import keyMap from '../jsutils/keyMap'; import keyValMap from '../jsutils/keyValMap'; import type { ObjMap } from '../jsutils/ObjMap'; import { valueFromAST } from './valueFromAST'; +import { assertValidSDL } from '../validation/validate'; import blockStringValue from '../language/blockStringValue'; import { TokenKind } from '../language/lexer'; import { parse } from '../language/parser'; @@ -86,6 +87,13 @@ export type BuildSchemaOptions = { * Default: false */ commentDescriptions?: boolean, + + /** + * Set to true to assume the SDL is valid. + * + * Default: false + */ + assumeValidSDL?: boolean, }; /** @@ -112,6 +120,10 @@ export function buildASTSchema( throw new Error('Must provide a document ast.'); } + if (!options || !(options.assumeValid || options.assumeValidSDL)) { + assertValidSDL(ast); + } + let schemaDef: ?SchemaDefinitionNode; const typeDefs: Array = []; @@ -121,9 +133,6 @@ export function buildASTSchema( const d = ast.definitions[i]; switch (d.kind) { case Kind.SCHEMA_DEFINITION: - if (schemaDef) { - throw new Error('Must provide only one schema definition.'); - } schemaDef = d; break; case Kind.SCALAR_TYPE_DEFINITION: diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 8405a55cf2..7be0a0d2c0 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -12,6 +12,7 @@ import keyMap from '../jsutils/keyMap'; import keyValMap from '../jsutils/keyValMap'; import objectValues from '../jsutils/objectValues'; import { ASTDefinitionBuilder } from './buildASTSchema'; +import { assertValidSDLExtension } from '../validation/validate'; import { GraphQLError } from '../error/GraphQLError'; import { isSchema, GraphQLSchema } from '../type/schema'; import { isIntrospectionType } from '../type/introspection'; @@ -67,6 +68,13 @@ type Options = {| * Default: false */ commentDescriptions?: boolean, + + /** + * Set to true to assume the SDL is valid. + * + * Default: false + */ + assumeValidSDL?: boolean, |}; /** @@ -99,6 +107,10 @@ export function extendSchema( 'Must provide valid Document AST', ); + if (!options || !(options.assumeValid || options.assumeValidSDL)) { + assertValidSDLExtension(documentAST, schema); + } + // Collect the type definitions and extensions found in the document. const typeDefinitionMap = Object.create(null); const typeExtensionsMap = Object.create(null); @@ -115,18 +127,6 @@ export function extendSchema( const def = documentAST.definitions[i]; switch (def.kind) { case Kind.SCHEMA_DEFINITION: - // Sanity check that a schema extension is not overriding the schema - if ( - schema.astNode || - schema.getQueryType() || - schema.getMutationType() || - schema.getSubscriptionType() - ) { - throw new GraphQLError( - 'Cannot define a new schema within a schema extension.', - [def], - ); - } schemaDef = def; break; case Kind.SCHEMA_EXTENSION: diff --git a/src/validation/ValidationContext.js b/src/validation/ValidationContext.js index ba7b358a0d..27b52439fc 100644 --- a/src/validation/ValidationContext.js +++ b/src/validation/ValidationContext.js @@ -19,6 +19,7 @@ import type { FragmentSpreadNode, FragmentDefinitionNode, } from '../language/ast'; +import type { ASTVisitor } from '../language/visitor'; import type { GraphQLSchema } from '../type/schema'; import type { GraphQLInputType, @@ -64,6 +65,21 @@ export class ASTValidationContext { } } +export class SDLValidationContext extends ASTValidationContext { + _schema: ?GraphQLSchema; + + constructor(ast: DocumentNode, schema?: ?GraphQLSchema): void { + super(ast); + this._schema = schema; + } + + getSchema(): ?GraphQLSchema { + return this._schema; + } +} + +export type SDLValidationRule = SDLValidationContext => ASTVisitor; + export class ValidationContext extends ASTValidationContext { _schema: GraphQLSchema; _typeInfo: TypeInfo; @@ -234,3 +250,5 @@ export class ValidationContext extends ASTValidationContext { return this._typeInfo.getArgument(); } } + +export type ValidationRule = ValidationContext => ASTVisitor; diff --git a/src/validation/__tests__/KnownDirectives-test.js b/src/validation/__tests__/KnownDirectives-test.js index 0e0daf5641..6e0787db08 100644 --- a/src/validation/__tests__/KnownDirectives-test.js +++ b/src/validation/__tests__/KnownDirectives-test.js @@ -6,13 +6,24 @@ */ import { describe, it } from 'mocha'; -import { expectPassesRule, expectFailsRule } from './harness'; +import { buildSchema } from '../../utilities'; +import { + expectPassesRule, + expectFailsRule, + expectSDLErrorsFromRule, +} from './harness'; + import { KnownDirectives, unknownDirectiveMessage, misplacedDirectiveMessage, } from '../rules/KnownDirectives'; +const expectSDLErrors = expectSDLErrorsFromRule.bind( + undefined, + KnownDirectives, +); + function unknownDirective(directiveName, line, column) { return { message: unknownDirectiveMessage(directiveName), @@ -27,6 +38,20 @@ function misplacedDirective(directiveName, placement, line, column) { }; } +const schemaWithSDLDirectives = buildSchema(` + directive @onSchema on SCHEMA + directive @onScalar on SCALAR + directive @onObject on OBJECT + directive @onFieldDefinition on FIELD_DEFINITION + directive @onArgumentDefinition on ARGUMENT_DEFINITION + directive @onInterface on INTERFACE + directive @onUnion on UNION + directive @onEnum on ENUM + directive @onEnumValue on ENUM_VALUE + directive @onInputObject on INPUT_OBJECT + directive @onInputFieldDefinition on INPUT_FIELD_DEFINITION +`); + describe('Validate: Known directives', () => { it('with no directives', () => { expectPassesRule( @@ -138,10 +163,36 @@ describe('Validate: Known directives', () => { ); }); - describe('within schema language', () => { + describe('within SDL', () => { + it('with directive defined inside SDL', () => { + expectSDLErrors(` + type Query { + foo: String @test + } + + directive @test on FIELD_DEFINITION + `).to.deep.equal([]); + }); + + it('with standard directive', () => { + expectSDLErrors(` + type Query { + foo: String @deprecated + } + `).to.deep.equal([]); + }); + + it('with overrided standard directive', () => { + expectSDLErrors(` + schema @deprecated { + query: Query + } + directive @deprecated on SCHEMA + `).to.deep.equal([]); + }); + it('with well placed directives', () => { - expectPassesRule( - KnownDirectives, + expectSDLErrors( ` type MyObj implements MyInterface @onObject { myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition @@ -180,13 +231,13 @@ describe('Validate: Known directives', () => { } extend schema @onSchema - `, - ); + `, + schemaWithSDLDirectives, + ).to.deep.equal([]); }); it('with misplaced directives', () => { - expectFailsRule( - KnownDirectives, + expectSDLErrors( ` type MyObj implements MyInterface @onInterface { myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition @@ -213,49 +264,39 @@ describe('Validate: Known directives', () => { } extend schema @onObject - `, - [ - misplacedDirective('onInterface', 'OBJECT', 2, 43), - misplacedDirective( - 'onInputFieldDefinition', - 'ARGUMENT_DEFINITION', - 3, - 30, - ), - misplacedDirective( - 'onInputFieldDefinition', - 'FIELD_DEFINITION', - 3, - 63, - ), - misplacedDirective('onEnum', 'SCALAR', 6, 25), - misplacedDirective('onObject', 'INTERFACE', 8, 31), - misplacedDirective( - 'onInputFieldDefinition', - 'ARGUMENT_DEFINITION', - 9, - 30, - ), - misplacedDirective( - 'onInputFieldDefinition', - 'FIELD_DEFINITION', - 9, - 63, - ), - misplacedDirective('onEnumValue', 'UNION', 12, 23), - misplacedDirective('onScalar', 'ENUM', 14, 21), - misplacedDirective('onUnion', 'ENUM_VALUE', 15, 20), - misplacedDirective('onEnum', 'INPUT_OBJECT', 18, 23), - misplacedDirective( - 'onArgumentDefinition', - 'INPUT_FIELD_DEFINITION', - 19, - 24, - ), - misplacedDirective('onObject', 'SCHEMA', 22, 16), - misplacedDirective('onObject', 'SCHEMA', 26, 23), - ], - ); + `, + schemaWithSDLDirectives, + ).to.deep.equal([ + misplacedDirective('onInterface', 'OBJECT', 2, 43), + misplacedDirective( + 'onInputFieldDefinition', + 'ARGUMENT_DEFINITION', + 3, + 30, + ), + misplacedDirective('onInputFieldDefinition', 'FIELD_DEFINITION', 3, 63), + misplacedDirective('onEnum', 'SCALAR', 6, 25), + misplacedDirective('onObject', 'INTERFACE', 8, 31), + misplacedDirective( + 'onInputFieldDefinition', + 'ARGUMENT_DEFINITION', + 9, + 30, + ), + misplacedDirective('onInputFieldDefinition', 'FIELD_DEFINITION', 9, 63), + misplacedDirective('onEnumValue', 'UNION', 12, 23), + misplacedDirective('onScalar', 'ENUM', 14, 21), + misplacedDirective('onUnion', 'ENUM_VALUE', 15, 20), + misplacedDirective('onEnum', 'INPUT_OBJECT', 18, 23), + misplacedDirective( + 'onArgumentDefinition', + 'INPUT_FIELD_DEFINITION', + 19, + 24, + ), + misplacedDirective('onObject', 'SCHEMA', 22, 16), + misplacedDirective('onObject', 'SCHEMA', 26, 23), + ]); }); }); }); diff --git a/src/validation/__tests__/UniqueDirectivesPerLocation-test.js b/src/validation/__tests__/UniqueDirectivesPerLocation-test.js index c423e906fe..31d010394f 100644 --- a/src/validation/__tests__/UniqueDirectivesPerLocation-test.js +++ b/src/validation/__tests__/UniqueDirectivesPerLocation-test.js @@ -6,12 +6,22 @@ */ import { describe, it } from 'mocha'; -import { expectPassesRule, expectFailsRule } from './harness'; +import { + expectPassesRule, + expectFailsRule, + expectSDLErrorsFromRule, +} from './harness'; + import { UniqueDirectivesPerLocation, duplicateDirectiveMessage, } from '../rules/UniqueDirectivesPerLocation'; +const expectSDLErrors = expectSDLErrorsFromRule.bind( + undefined, + UniqueDirectivesPerLocation, +); + function duplicateDirective(directiveName, l1, c1, l2, c2) { return { message: duplicateDirectiveMessage(directiveName), @@ -132,4 +142,39 @@ describe('Validate: Directives Are Unique Per Location', () => { ], ); }); + + it('duplicate directives on SDL definitions', () => { + expectSDLErrors(` + schema @directive @directive { query: Dummy } + extend schema @directive @directive + + scalar TestScalar @directive @directive + extend scalar TestScalar @directive @directive + + type TestObject @directive @directive + extend type TestObject @directive @directive + + interface TestInterface @directive @directive + extend interface TestInterface @directive @directive + + union TestUnion @directive @directive + extend union TestUnion @directive @directive + + input TestInput @directive @directive + extend input TestInput @directive @directive + `).to.deep.equal([ + duplicateDirective('directive', 2, 14, 2, 25), + duplicateDirective('directive', 3, 21, 3, 32), + duplicateDirective('directive', 5, 25, 5, 36), + duplicateDirective('directive', 6, 32, 6, 43), + duplicateDirective('directive', 8, 23, 8, 34), + duplicateDirective('directive', 9, 30, 9, 41), + duplicateDirective('directive', 11, 31, 11, 42), + duplicateDirective('directive', 12, 38, 12, 49), + duplicateDirective('directive', 14, 23, 14, 34), + duplicateDirective('directive', 15, 30, 15, 41), + duplicateDirective('directive', 17, 23, 17, 34), + duplicateDirective('directive', 18, 30, 18, 41), + ]); + }); }); diff --git a/src/validation/__tests__/harness.js b/src/validation/__tests__/harness.js index 1c41be6031..157c5f3777 100644 --- a/src/validation/__tests__/harness.js +++ b/src/validation/__tests__/harness.js @@ -7,7 +7,7 @@ import { expect } from 'chai'; import { parse } from '../../language'; -import { validate } from '../validate'; +import { validate, validateSDL } from '../validate'; import { GraphQLSchema, GraphQLObjectType, @@ -374,50 +374,6 @@ export const testSchema = new GraphQLSchema({ name: 'onInlineFragment', locations: ['INLINE_FRAGMENT'], }), - new GraphQLDirective({ - name: 'onSchema', - locations: ['SCHEMA'], - }), - new GraphQLDirective({ - name: 'onScalar', - locations: ['SCALAR'], - }), - new GraphQLDirective({ - name: 'onObject', - locations: ['OBJECT'], - }), - new GraphQLDirective({ - name: 'onFieldDefinition', - locations: ['FIELD_DEFINITION'], - }), - new GraphQLDirective({ - name: 'onArgumentDefinition', - locations: ['ARGUMENT_DEFINITION'], - }), - new GraphQLDirective({ - name: 'onInterface', - locations: ['INTERFACE'], - }), - new GraphQLDirective({ - name: 'onUnion', - locations: ['UNION'], - }), - new GraphQLDirective({ - name: 'onEnum', - locations: ['ENUM'], - }), - new GraphQLDirective({ - name: 'onEnumValue', - locations: ['ENUM_VALUE'], - }), - new GraphQLDirective({ - name: 'onInputObject', - locations: ['INPUT_OBJECT'], - }), - new GraphQLDirective({ - name: 'onInputFieldDefinition', - locations: ['INPUT_FIELD_DEFINITION'], - }), ], }); @@ -448,3 +404,8 @@ export function expectPassesRuleWithSchema(schema, rule, queryString) { export function expectFailsRuleWithSchema(schema, rule, queryString, errors) { return expectInvalid(schema, rule, queryString, errors); } + +export function expectSDLErrorsFromRule(rule, sdlString, schema) { + const errors = validateSDL(parse(sdlString), schema, [rule]); + return expect(errors); +} diff --git a/src/validation/rules/KnownDirectives.js b/src/validation/rules/KnownDirectives.js index c812f8e23f..37f273fbe6 100644 --- a/src/validation/rules/KnownDirectives.js +++ b/src/validation/rules/KnownDirectives.js @@ -7,12 +7,15 @@ * @flow strict */ -import type { ValidationContext } from '../ValidationContext'; +import type { + ValidationContext, + SDLValidationContext, +} from '../ValidationContext'; import { GraphQLError } from '../../error'; -import find from '../../jsutils/find'; import { Kind } from '../../language/kinds'; import { DirectiveLocation } from '../../language/directiveLocation'; import type { ASTVisitor } from '../../language/visitor'; +import { specifiedDirectives } from '../../type/directives'; export function unknownDirectiveMessage(directiveName: string): string { return `Unknown directive "${directiveName}".`; @@ -31,29 +34,42 @@ export function misplacedDirectiveMessage( * A GraphQL document is only valid if all `@directives` are known by the * schema and legally positioned. */ -export function KnownDirectives(context: ValidationContext): ASTVisitor { +export function KnownDirectives( + context: ValidationContext | SDLValidationContext, +): ASTVisitor { + const locationsMap = Object.create(null); + const schema = context.getSchema(); + const definedDirectives = schema + ? schema.getDirectives() + : specifiedDirectives; + for (const directive of definedDirectives) { + locationsMap[directive.name] = directive.locations; + } + + const astDefinitions = context.getDocument().definitions; + for (const def of astDefinitions) { + if (def.kind === Kind.DIRECTIVE_DEFINITION) { + locationsMap[def.name.value] = def.locations.map(name => name.value); + } + } + return { Directive(node, key, parent, path, ancestors) { - const directiveDef = find( - context.getSchema().getDirectives(), - def => def.name === node.name.value, - ); - if (!directiveDef) { + const name = node.name.value; + const locations = locationsMap[name]; + + if (!locations) { context.reportError( - new GraphQLError(unknownDirectiveMessage(node.name.value), [node]), + new GraphQLError(unknownDirectiveMessage(name), [node]), ); return; } const candidateLocation = getDirectiveLocationForASTPath(ancestors); - if ( - candidateLocation && - directiveDef.locations.indexOf(candidateLocation) === -1 - ) { + if (candidateLocation && locations.indexOf(candidateLocation) === -1) { context.reportError( - new GraphQLError( - misplacedDirectiveMessage(node.name.value, candidateLocation), - [node], - ), + new GraphQLError(misplacedDirectiveMessage(name, candidateLocation), [ + node, + ]), ); } }, diff --git a/src/validation/rules/LoneSchemaDefinition.js b/src/validation/rules/LoneSchemaDefinition.js new file mode 100644 index 0000000000..e76f2d94be --- /dev/null +++ b/src/validation/rules/LoneSchemaDefinition.js @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +import type { SDLValidationContext } from '../ValidationContext'; +import { GraphQLError } from '../../error'; +import type { ASTVisitor } from '../../language/visitor'; + +export function schemaDefinitionNotAloneMessage(): string { + return 'Must provide only one schema definition.'; +} + +export function canNotDefineSchemaWithinExtension(): string { + return 'Cannot define a new schema within a schema extension.'; +} + +/** + * Lone Schema definition + * + * A GraphQL document is only valid if it contains only one schema definition. + */ +export function LoneSchemaDefinition( + context: SDLValidationContext, +): ASTVisitor { + const oldSchema = context.getSchema(); + const alreadyDefined = + oldSchema && + (oldSchema.astNode || + oldSchema.getQueryType() || + oldSchema.getMutationType() || + oldSchema.getSubscriptionType()); + + const schemaNodes = []; + return { + SchemaDefinition(node) { + if (alreadyDefined) { + context.reportError( + new GraphQLError(canNotDefineSchemaWithinExtension(), [node]), + ); + return; + } + schemaNodes.push(node); + }, + Document: { + leave() { + if (schemaNodes.length > 1) { + context.reportError( + new GraphQLError(schemaDefinitionNotAloneMessage(), schemaNodes), + ); + } + }, + }, + }; +} diff --git a/src/validation/specifiedRules.js b/src/validation/specifiedRules.js index 63677e154e..4bf2b31164 100644 --- a/src/validation/specifiedRules.js +++ b/src/validation/specifiedRules.js @@ -7,8 +7,7 @@ * @flow strict */ -import type { ASTVisitor } from '../language/visitor'; -import type { ValidationContext } from './ValidationContext'; +import type { ValidationRule, SDLValidationRule } from './ValidationContext'; // Spec Section: "Executable Definitions" import { ExecutableDefinitions } from './rules/ExecutableDefinitions'; @@ -94,7 +93,7 @@ import { UniqueInputFieldNames } from './rules/UniqueInputFieldNames'; * The order of the rules in this list has been adjusted to lead to the * most clear output when encountering multiple validation errors. */ -export const specifiedRules: Array<(ValidationContext) => ASTVisitor> = [ +export const specifiedRules: $ReadOnlyArray = [ ExecutableDefinitions, UniqueOperationNames, LoneAnonymousOperation, @@ -122,3 +121,14 @@ export const specifiedRules: Array<(ValidationContext) => ASTVisitor> = [ OverlappingFieldsCanBeMerged, UniqueInputFieldNames, ]; + +import { LoneSchemaDefinition } from './rules/LoneSchemaDefinition'; + +// @internal +export const specifiedSDLRules: $ReadOnlyArray = [ + LoneSchemaDefinition, + KnownDirectives, + UniqueDirectivesPerLocation, + UniqueArgumentNames, + UniqueInputFieldNames, +]; diff --git a/src/validation/validate.js b/src/validation/validate.js index bde2f699e3..9fe1b2ad86 100644 --- a/src/validation/validate.js +++ b/src/validation/validate.js @@ -9,14 +9,14 @@ import invariant from '../jsutils/invariant'; import type { GraphQLError } from '../error'; -import type { ASTVisitor } from '../language/visitor'; import { visit, visitInParallel, visitWithTypeInfo } from '../language/visitor'; import type { DocumentNode } from '../language/ast'; import type { GraphQLSchema } from '../type/schema'; import { assertValidSchema } from '../type/validate'; import { TypeInfo } from '../utilities/TypeInfo'; -import { specifiedRules } from './specifiedRules'; -import { ValidationContext } from './ValidationContext'; +import { specifiedRules, specifiedSDLRules } from './specifiedRules'; +import type { SDLValidationRule, ValidationRule } from './ValidationContext'; +import { SDLValidationContext, ValidationContext } from './ValidationContext'; /** * Implements the "Validation" section of the spec. @@ -37,7 +37,7 @@ import { ValidationContext } from './ValidationContext'; export function validate( schema: GraphQLSchema, documentAST: DocumentNode, - rules?: $ReadOnlyArray<(ValidationContext) => ASTVisitor> = specifiedRules, + rules?: $ReadOnlyArray = specifiedRules, typeInfo?: TypeInfo = new TypeInfo(schema), ): $ReadOnlyArray { invariant(documentAST, 'Must provide document'); @@ -52,3 +52,44 @@ export function validate( visit(documentAST, visitWithTypeInfo(typeInfo, visitor)); return context.getErrors(); } + +// @internal +export function validateSDL( + documentAST: DocumentNode, + schemaToExtend?: ?GraphQLSchema, + rules?: $ReadOnlyArray = specifiedSDLRules, +): $ReadOnlyArray { + const context = new SDLValidationContext(documentAST, schemaToExtend); + const visitors = rules.map(rule => rule(context)); + visit(documentAST, visitInParallel(visitors)); + return context.getErrors(); +} + +/** + * Utility function which asserts a SDL document is valid by throwing an error + * if it is invalid. + * + * @internal + */ +export function assertValidSDL(documentAST: DocumentNode): void { + const errors = validateSDL(documentAST); + if (errors.length !== 0) { + throw new Error(errors.map(error => error.message).join('\n\n')); + } +} + +/** + * Utility function which asserts a SDL document is valid by throwing an error + * if it is invalid. + * + * @internal + */ +export function assertValidSDLExtension( + documentAST: DocumentNode, + schema: GraphQLSchema, +): void { + const errors = validateSDL(documentAST, schema); + if (errors.length !== 0) { + throw new Error(errors.map(error => error.message).join('\n\n')); + } +}