diff --git a/src/index.js b/src/index.js index a3d2f7bc80..c439340a0a 100644 --- a/src/index.js +++ b/src/index.js @@ -245,6 +245,9 @@ export type { EnumTypeDefinitionNode, EnumValueDefinitionNode, InputObjectTypeDefinitionNode, + DirectiveDefinitionNode, + TypeSystemExtensionNode, + SchemaExtensionNode, TypeExtensionNode, ScalarTypeExtensionNode, ObjectTypeExtensionNode, @@ -252,7 +255,6 @@ export type { UnionTypeExtensionNode, EnumTypeExtensionNode, InputObjectTypeExtensionNode, - DirectiveDefinitionNode, KindEnum, TokenKindEnum, DirectiveLocationEnum, diff --git a/src/language/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql index 5ce4ca6a06..1c7b5c3b30 100644 --- a/src/language/__tests__/schema-kitchen-sink.graphql +++ b/src/language/__tests__/schema-kitchen-sink.graphql @@ -123,3 +123,9 @@ directive @include2(if: Boolean!) on | FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +extend schema @onSchema + +extend schema @onSchema { + subscription: SubscriptionType +} diff --git a/src/language/__tests__/schema-parser-test.js b/src/language/__tests__/schema-parser-test.js index 1c9fe0f144..c35e99a4f3 100644 --- a/src/language/__tests__/schema-parser-test.js +++ b/src/language/__tests__/schema-parser-test.js @@ -263,6 +263,66 @@ extend type Hello { ); }); + it('Schema extension', () => { + const body = ` + extend schema { + mutation: Mutation + }`; + const doc = parse(body); + const expected = { + kind: 'Document', + definitions: [ + { + kind: 'SchemaExtension', + directives: [], + operationTypes: [ + { + kind: 'OperationTypeDefinition', + operation: 'mutation', + type: typeNode('Mutation', { start: 41, end: 49 }), + loc: { start: 31, end: 49 }, + }, + ], + loc: { start: 7, end: 57 }, + }, + ], + loc: { start: 0, end: 57 }, + }; + expect(printJson(doc)).to.equal(printJson(expected)); + }); + + it('Schema extension with only directives', () => { + const body = 'extend schema @directive'; + const doc = parse(body); + const expected = { + kind: 'Document', + definitions: [ + { + kind: 'SchemaExtension', + directives: [ + { + kind: 'Directive', + name: nameNode('directive', { start: 15, end: 24 }), + arguments: [], + loc: { start: 14, end: 24 }, + }, + ], + operationTypes: [], + loc: { start: 0, end: 24 }, + }, + ], + loc: { start: 0, end: 24 }, + }; + expect(printJson(doc)).to.equal(printJson(expected)); + }); + + it('Schema extension without anything throws', () => { + expectSyntaxError('extend schema', 'Unexpected ', { + line: 1, + column: 14, + }); + }); + it('Simple non-null type', () => { const body = ` type Hello { diff --git a/src/language/__tests__/schema-printer-test.js b/src/language/__tests__/schema-printer-test.js index 00dd5d70b3..3803bd59cc 100644 --- a/src/language/__tests__/schema-printer-test.js +++ b/src/language/__tests__/schema-printer-test.js @@ -161,6 +161,12 @@ describe('Printer: SDL document', () => { directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + + extend schema @onSchema + + extend schema @onSchema { + subscription: SubscriptionType + } `); }); }); diff --git a/src/language/ast.js b/src/language/ast.js index 6b6f5fbcd2..000bc3bdad 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -124,13 +124,14 @@ export type ASTNode = | EnumTypeDefinitionNode | EnumValueDefinitionNode | InputObjectTypeDefinitionNode + | DirectiveDefinitionNode + | SchemaExtensionNode | ScalarTypeExtensionNode | ObjectTypeExtensionNode | InterfaceTypeExtensionNode | UnionTypeExtensionNode | EnumTypeExtensionNode - | InputObjectTypeExtensionNode - | DirectiveDefinitionNode; + | InputObjectTypeExtensionNode; /** * Utility type listing all nodes indexed by their kind. @@ -171,13 +172,14 @@ export type ASTKindToNode = { EnumTypeDefinition: EnumTypeDefinitionNode, EnumValueDefinition: EnumValueDefinitionNode, InputObjectTypeDefinition: InputObjectTypeDefinitionNode, + DirectiveDefinition: DirectiveDefinitionNode, + SchemaExtension: SchemaExtensionNode, ScalarTypeExtension: ScalarTypeExtensionNode, ObjectTypeExtension: ObjectTypeExtensionNode, InterfaceTypeExtension: InterfaceTypeExtensionNode, UnionTypeExtension: UnionTypeExtensionNode, EnumTypeExtension: EnumTypeExtensionNode, InputObjectTypeExtension: InputObjectTypeExtensionNode, - DirectiveDefinition: DirectiveDefinitionNode, }; // Name @@ -198,7 +200,8 @@ export type DocumentNode = { export type DefinitionNode = | ExecutableDefinitionNode - | TypeSystemDefinitionNode; // experimental non-spec addition. + | TypeSystemDefinitionNode + | TypeSystemExtensionNode; export type ExecutableDefinitionNode = | OperationDefinitionNode @@ -388,13 +391,12 @@ export type NonNullTypeNode = { export type TypeSystemDefinitionNode = | SchemaDefinitionNode | TypeDefinitionNode - | TypeExtensionNode | DirectiveDefinitionNode; export type SchemaDefinitionNode = { +kind: 'SchemaDefinition', +loc?: Location, - +directives: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +operationTypes: $ReadOnlyArray, }; @@ -497,6 +499,28 @@ export type InputObjectTypeDefinitionNode = { +fields?: $ReadOnlyArray, }; +// Directive Definitions + +export type DirectiveDefinitionNode = { + +kind: 'DirectiveDefinition', + +loc?: Location, + +description?: StringValueNode, + +name: NameNode, + +arguments?: $ReadOnlyArray, + +locations: $ReadOnlyArray, +}; + +// Type System Extensions + +export type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode; + +export type SchemaExtensionNode = { + +kind: 'SchemaExtension', + +loc?: Location, + +directives?: $ReadOnlyArray, + +operationTypes?: $ReadOnlyArray, +}; + // Type Extensions export type TypeExtensionNode = @@ -554,14 +578,3 @@ export type InputObjectTypeExtensionNode = { +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, }; - -// Directive Definitions - -export type DirectiveDefinitionNode = { - +kind: 'DirectiveDefinition', - +loc?: Location, - +description?: StringValueNode, - +name: NameNode, - +arguments?: $ReadOnlyArray, - +locations: $ReadOnlyArray, -}; diff --git a/src/language/index.js b/src/language/index.js index 874eedf9e7..e49e8917d1 100644 --- a/src/language/index.js +++ b/src/language/index.js @@ -76,6 +76,9 @@ export type { EnumTypeDefinitionNode, EnumValueDefinitionNode, InputObjectTypeDefinitionNode, + DirectiveDefinitionNode, + TypeSystemExtensionNode, + SchemaExtensionNode, TypeExtensionNode, ScalarTypeExtensionNode, ObjectTypeExtensionNode, @@ -83,7 +86,6 @@ export type { UnionTypeExtensionNode, EnumTypeExtensionNode, InputObjectTypeExtensionNode, - DirectiveDefinitionNode, } from './ast'; export { DirectiveLocation } from './directiveLocation'; diff --git a/src/language/kinds.js b/src/language/kinds.js index d67f748e34..c6f1587ee4 100644 --- a/src/language/kinds.js +++ b/src/language/kinds.js @@ -62,6 +62,12 @@ export const Kind = Object.freeze({ ENUM_VALUE_DEFINITION: 'EnumValueDefinition', INPUT_OBJECT_TYPE_DEFINITION: 'InputObjectTypeDefinition', + // Directive Definitions + DIRECTIVE_DEFINITION: 'DirectiveDefinition', + + // Type System Extensions + SCHEMA_EXTENSION: 'SchemaExtension', + // Type Extensions SCALAR_TYPE_EXTENSION: 'ScalarTypeExtension', OBJECT_TYPE_EXTENSION: 'ObjectTypeExtension', @@ -69,9 +75,6 @@ export const Kind = Object.freeze({ UNION_TYPE_EXTENSION: 'UnionTypeExtension', ENUM_TYPE_EXTENSION: 'EnumTypeExtension', INPUT_OBJECT_TYPE_EXTENSION: 'InputObjectTypeExtension', - - // Directive Definitions - DIRECTIVE_DEFINITION: 'DirectiveDefinition', }); /** diff --git a/src/language/parser.js b/src/language/parser.js index 20120eeb67..6fe84fe9da 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -52,14 +52,15 @@ import type { EnumTypeDefinitionNode, EnumValueDefinitionNode, InputObjectTypeDefinitionNode, - TypeExtensionNode, + DirectiveDefinitionNode, + TypeSystemExtensionNode, + SchemaExtensionNode, ScalarTypeExtensionNode, ObjectTypeExtensionNode, InterfaceTypeExtensionNode, UnionTypeExtensionNode, EnumTypeExtensionNode, InputObjectTypeExtensionNode, - DirectiveDefinitionNode, } from './ast'; import { Kind } from './kinds'; @@ -211,6 +212,7 @@ function parseDocument(lexer: Lexer<*>): DocumentNode { * Definition : * - ExecutableDefinition * - TypeSystemDefinition + * - TypeSystemExtension */ function parseDefinition(lexer: Lexer<*>): DefinitionNode { if (peek(lexer, TokenKind.NAME)) { @@ -227,15 +229,14 @@ function parseDefinition(lexer: Lexer<*>): DefinitionNode { case 'union': case 'enum': case 'input': - case 'extend': case 'directive': - // Note: The schema definition language is an experimental addition. return parseTypeSystemDefinition(lexer); + case 'extend': + return parseTypeSystemExtension(lexer); } } else if (peek(lexer, TokenKind.BRACE_L)) { return parseExecutableDefinition(lexer); } else if (peekDescription(lexer)) { - // Note: The schema definition language is an experimental addition. return parseTypeSystemDefinition(lexer); } @@ -753,7 +754,6 @@ export function parseNamedType(lexer: Lexer<*>): NamedTypeNode { * TypeSystemDefinition : * - SchemaDefinition * - TypeDefinition - * - TypeExtension * - DirectiveDefinition * * TypeDefinition : @@ -784,8 +784,6 @@ function parseTypeSystemDefinition(lexer: Lexer<*>): TypeSystemDefinitionNode { return parseEnumTypeDefinition(lexer); case 'input': return parseInputObjectTypeDefinition(lexer); - case 'extend': - return parseTypeExtension(lexer); case 'directive': return parseDirectiveDefinition(lexer); } @@ -1141,6 +1139,10 @@ function parseInputFieldsDefinition( } /** + * TypeSystemExtension : + * - SchemaExtension + * - TypeExtension + * * TypeExtension : * - ScalarTypeExtension * - ObjectTypeExtension @@ -1149,11 +1151,13 @@ function parseInputFieldsDefinition( * - EnumTypeExtension * - InputObjectTypeDefinition */ -function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode { +function parseTypeSystemExtension(lexer: Lexer<*>): TypeSystemExtensionNode { const keywordToken = lexer.lookahead(); if (keywordToken.kind === TokenKind.NAME) { switch (keywordToken.value) { + case 'schema': + return parseSchemaExtension(lexer); case 'scalar': return parseScalarTypeExtension(lexer); case 'type': @@ -1172,6 +1176,35 @@ function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode { throw unexpected(lexer, keywordToken); } +/** + * SchemaExtension : + * - extend schema Directives[Const]? { OperationTypeDefinition+ } + * - extend schema Directives[Const] + */ +function parseSchemaExtension(lexer: Lexer<*>): SchemaExtensionNode { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + expectKeyword(lexer, 'schema'); + const directives = parseDirectives(lexer, true); + const operationTypes = peek(lexer, TokenKind.BRACE_L) + ? many( + lexer, + TokenKind.BRACE_L, + parseOperationTypeDefinition, + TokenKind.BRACE_R, + ) + : []; + if (directives.length === 0 && operationTypes.length === 0) { + throw unexpected(lexer); + } + return { + kind: Kind.SCHEMA_EXTENSION, + directives, + operationTypes, + loc: loc(lexer, start), + }; +} + /** * ScalarTypeExtension : * - extend scalar Name Directives[Const] diff --git a/src/language/printer.js b/src/language/printer.js index 558b0f7e0e..0242898f52 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -175,6 +175,20 @@ const printDocASTReducer = { join(['input', name, join(directives, ' '), block(fields)], ' '), ), + DirectiveDefinition: addDescription( + ({ name, arguments: args, locations }) => + 'directive @' + + name + + (args.every(arg => arg.indexOf('\n') === -1) + ? wrap('(', join(args, ', '), ')') + : wrap('(\n', indent(join(args, '\n')), '\n)')) + + ' on ' + + join(locations, ' | '), + ), + + SchemaExtension: ({ directives, operationTypes }) => + join(['extend schema', join(directives, ' '), block(operationTypes)], ' '), + ScalarTypeExtension: ({ name, directives }) => join(['extend scalar', name, join(directives, ' ')], ' '), @@ -209,17 +223,6 @@ const printDocASTReducer = { InputObjectTypeExtension: ({ name, directives, fields }) => join(['extend input', name, join(directives, ' '), block(fields)], ' '), - - DirectiveDefinition: addDescription( - ({ name, arguments: args, locations }) => - 'directive @' + - name + - (args.every(arg => arg.indexOf('\n') === -1) - ? wrap('(', join(args, ', '), ')') - : wrap('(\n', indent(join(args, '\n')), '\n)')) + - ' on ' + - join(locations, ' | '), - ), }; function addDescription(cb) { diff --git a/src/language/visitor.js b/src/language/visitor.js index ef166e3726..00e4510992 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -123,14 +123,16 @@ export const QueryDocumentKeys = { EnumValueDefinition: ['description', 'name', 'directives'], InputObjectTypeDefinition: ['description', 'name', 'directives', 'fields'], + DirectiveDefinition: ['description', 'name', 'arguments', 'locations'], + + SchemaExtension: ['directives', 'operationTypes'], + ScalarTypeExtension: ['name', 'directives'], ObjectTypeExtension: ['name', 'interfaces', 'directives', 'fields'], InterfaceTypeExtension: ['name', 'directives', 'fields'], UnionTypeExtension: ['name', 'directives', 'types'], EnumTypeExtension: ['name', 'directives', 'values'], InputObjectTypeExtension: ['name', 'directives', 'fields'], - - DirectiveDefinition: ['description', 'name', 'arguments', 'locations'], }; export const BREAK = {}; diff --git a/src/type/schema.js b/src/type/schema.js index 4a6f917890..96ac0167b3 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -21,7 +21,10 @@ import type { GraphQLAbstractType, GraphQLObjectType, } from './definition'; -import type { SchemaDefinitionNode } from '../language/ast'; +import type { + SchemaDefinitionNode, + SchemaExtensionNode, +} from '../language/ast'; import { GraphQLDirective, isDirective, @@ -73,6 +76,7 @@ export function isSchema(schema) { */ export class GraphQLSchema { astNode: ?SchemaDefinitionNode; + extensionASTNodes: ?$ReadOnlyArray; _queryType: ?GraphQLObjectType; _mutationType: ?GraphQLObjectType; _subscriptionType: ?GraphQLObjectType; @@ -120,6 +124,7 @@ export class GraphQLSchema { // Provide specified directives (e.g. @include and @skip) by default. this._directives = config.directives || specifiedDirectives; this.astNode = config.astNode; + this.extensionASTNodes = config.extensionASTNodes; // Build type map now to detect any errors within this schema. let initialTypes: Array = [ @@ -255,6 +260,7 @@ export type GraphQLSchemaConfig = { types?: ?Array, directives?: ?Array, astNode?: ?SchemaDefinitionNode, + extensionASTNodes?: ?$ReadOnlyArray, ...GraphQLSchemaValidationOptions, }; diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index 857d93b593..05740c6a35 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -876,4 +876,182 @@ describe('extendSchema', () => { ); }); }); + + describe('can add additional root operation types', () => { + it('does not automatically include common root type names', () => { + const ast = parse(` + type Mutation { + doSomething: String + } + `); + const schema = extendSchema(testSchema, ast); + expect(schema.getMutationType()).to.equal(null); + }); + + it('does not allow new schema within an extension', () => { + const ast = parse(` + schema { + mutation: Mutation + } + + type Mutation { + doSomething: String + } + `); + expect(() => extendSchema(testSchema, ast)).to.throw( + 'Cannot define a new schema within a schema extension.', + ); + }); + + it('adds new root types via schema extension', () => { + const ast = parse(` + extend schema { + mutation: Mutation + } + + type Mutation { + doSomething: String + } + `); + const schema = extendSchema(testSchema, ast); + const mutationType = schema.getMutationType(); + expect(mutationType && mutationType.name).to.equal('Mutation'); + }); + + it('adds multiple new root types via schema extension', () => { + const ast = parse(` + extend schema { + mutation: Mutation + subscription: Subscription + } + + type Mutation { + doSomething: String + } + + type Subscription { + hearSomething: String + } + `); + const schema = extendSchema(testSchema, ast); + const mutationType = schema.getMutationType(); + const subscriptionType = schema.getSubscriptionType(); + expect(mutationType && mutationType.name).to.equal('Mutation'); + expect(subscriptionType && subscriptionType.name).to.equal( + 'Subscription', + ); + }); + + it('applies multiple schema extensions', () => { + const ast = parse(` + extend schema { + mutation: Mutation + } + + extend schema { + subscription: Subscription + } + + type Mutation { + doSomething: String + } + + type Subscription { + hearSomething: String + } + `); + const schema = extendSchema(testSchema, ast); + const mutationType = schema.getMutationType(); + const subscriptionType = schema.getSubscriptionType(); + expect(mutationType && mutationType.name).to.equal('Mutation'); + expect(subscriptionType && subscriptionType.name).to.equal( + 'Subscription', + ); + }); + + it('schema extension AST are available from schema object', () => { + const ast = parse(` + extend schema { + mutation: Mutation + } + + extend schema { + subscription: Subscription + } + + type Mutation { + doSomething: String + } + + type Subscription { + hearSomething: String + } + `); + const schema = extendSchema(testSchema, ast); + expect(schema.extensionASTNodes.map(print).join('\n')).to.equal(dedent` + extend schema { + mutation: Mutation + } + extend schema { + subscription: Subscription + }`); + }); + + it('does not allow redefining an existing root type', () => { + const ast = parse(` + extend schema { + query: SomeType + } + + type SomeType { + seeSomething: String + } + `); + expect(() => extendSchema(testSchema, ast)).to.throw( + 'Must provide only one query type in schema.', + ); + }); + + it('does not allow defining a root operation type twice', () => { + const ast = parse(` + extend schema { + mutation: Mutation + } + + extend schema { + mutation: Mutation + } + + type Mutation { + doSomething: String + } + `); + expect(() => extendSchema(testSchema, ast)).to.throw( + 'Must provide only one mutation type in schema.', + ); + }); + + it('does not allow defining a root operation type with different types', () => { + const ast = parse(` + extend schema { + mutation: Mutation + } + + extend schema { + mutation: SomethingElse + } + + type Mutation { + doSomething: String + } + + type SomethingElse { + doSomethingElse: String + } + `); + expect(() => extendSchema(testSchema, ast)).to.throw( + 'Must provide only one mutation type in schema.', + ); + }); + }); }); diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index cab1321acb..49fe0fab25 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -35,7 +35,11 @@ import { GraphQLDirective } from '../type/directives'; import { Kind } from '../language/kinds'; import type { GraphQLType, GraphQLNamedType } from '../type/definition'; -import type { DocumentNode, DirectiveDefinitionNode } from '../language/ast'; +import type { + DocumentNode, + DirectiveDefinitionNode, + SchemaExtensionNode, +} from '../language/ast'; type Options = {| ...GraphQLSchemaValidationOptions, @@ -88,9 +92,21 @@ export function extendSchema( // have the same name. For example, a type named "skip". const directiveDefinitions: Array = []; + // Schema extensions are collected which may add additional operation types. + const schemaExtensions: Array = []; + for (let i = 0; i < documentAST.definitions.length; i++) { const def = documentAST.definitions[i]; switch (def.kind) { + case Kind.SCHEMA_DEFINITION: + // Sanity check that a schema extension is not defining a new schema + throw new GraphQLError( + 'Cannot define a new schema within a schema extension.', + [def], + ); + case Kind.SCHEMA_EXTENSION: + schemaExtensions.push(def); + break; case Kind.OBJECT_TYPE_DEFINITION: case Kind.INTERFACE_TYPE_DEFINITION: case Kind.ENUM_TYPE_DEFINITION: @@ -156,7 +172,8 @@ export function extendSchema( if ( Object.keys(typeExtensionsMap).length === 0 && Object.keys(typeDefinitionMap).length === 0 && - directiveDefinitions.length === 0 + directiveDefinitions.length === 0 && + schemaExtensions.length === 0 ) { return schema; } @@ -181,21 +198,32 @@ export function extendSchema( const extendTypeCache = Object.create(null); - // Get the root Query, Mutation, and Subscription object types. + // Get the extended root operation types. const existingQueryType = schema.getQueryType(); - const queryType = existingQueryType - ? getExtendedType(existingQueryType) - : null; - const existingMutationType = schema.getMutationType(); - const mutationType = existingMutationType - ? getExtendedType(existingMutationType) - : null; - const existingSubscriptionType = schema.getSubscriptionType(); - const subscriptionType = existingSubscriptionType - ? getExtendedType(existingSubscriptionType) - : null; + const operationTypes = { + query: existingQueryType ? getExtendedType(existingQueryType) : null, + mutation: existingMutationType + ? getExtendedType(existingMutationType) + : null, + subscription: existingSubscriptionType + ? getExtendedType(existingSubscriptionType) + : null, + }; + + // Then, incorporate all schema extensions. + schemaExtensions.forEach(schemaExtension => { + if (schemaExtension.operationTypes) { + schemaExtension.operationTypes.forEach(operationType => { + const operation = operationType.operation; + if (operationTypes[operation]) { + throw new Error(`Must provide only one ${operation} type in schema.`); + } + operationTypes[operation] = astBuilder.buildType(operationType.type); + }); + } + }); const types = [ // Iterate through all types, getting the type definition for each, ensuring @@ -215,12 +243,13 @@ export function extendSchema( // Then produce and return a Schema with these types. return new GraphQLSchema({ - query: queryType, - mutation: mutationType, - subscription: subscriptionType, + query: operationTypes.query, + mutation: operationTypes.mutation, + subscription: operationTypes.subscription, types, directives: getMergedDirectives(), astNode: schema.astNode, + extensionASTNodes: schemaExtensions, allowedLegacyNames, }); diff --git a/src/validation/__tests__/ExecutableDefinitions-test.js b/src/validation/__tests__/ExecutableDefinitions-test.js index 3a626a3143..922420bb32 100644 --- a/src/validation/__tests__/ExecutableDefinitions-test.js +++ b/src/validation/__tests__/ExecutableDefinitions-test.js @@ -87,10 +87,13 @@ describe('Validate: Executable definitions', () => { type Query { test: String } + + extend schema @directive `, [ nonExecutableDefinition('schema', 2, 7), nonExecutableDefinition('Query', 6, 7), + nonExecutableDefinition('schema', 10, 7), ], ); }); diff --git a/src/validation/__tests__/KnownDirectives-test.js b/src/validation/__tests__/KnownDirectives-test.js index ae46029650..0e0daf5641 100644 --- a/src/validation/__tests__/KnownDirectives-test.js +++ b/src/validation/__tests__/KnownDirectives-test.js @@ -178,6 +178,8 @@ describe('Validate: Known directives', () => { schema @onSchema { query: MyQuery } + + extend schema @onSchema `, ); }); @@ -209,6 +211,8 @@ describe('Validate: Known directives', () => { schema @onObject { query: MyQuery } + + extend schema @onObject `, [ misplacedDirective('onInterface', 'OBJECT', 2, 43), @@ -249,6 +253,7 @@ describe('Validate: Known directives', () => { 24, ), misplacedDirective('onObject', 'SCHEMA', 22, 16), + misplacedDirective('onObject', 'SCHEMA', 26, 23), ], ); }); diff --git a/src/validation/rules/ExecutableDefinitions.js b/src/validation/rules/ExecutableDefinitions.js index b7b2d9a382..ac6a5a4bb9 100644 --- a/src/validation/rules/ExecutableDefinitions.js +++ b/src/validation/rules/ExecutableDefinitions.js @@ -33,7 +33,8 @@ export function ExecutableDefinitions(context: ValidationContext): ASTVisitor { context.reportError( new GraphQLError( nonExecutableDefinitionMessage( - definition.kind === Kind.SCHEMA_DEFINITION + definition.kind === Kind.SCHEMA_DEFINITION || + definition.kind === Kind.SCHEMA_EXTENSION ? 'schema' : definition.name.value, ), diff --git a/src/validation/rules/KnownDirectives.js b/src/validation/rules/KnownDirectives.js index 9972ce8acf..05c12f39f6 100644 --- a/src/validation/rules/KnownDirectives.js +++ b/src/validation/rules/KnownDirectives.js @@ -83,6 +83,7 @@ function getDirectiveLocationForASTPath(ancestors) { case Kind.FRAGMENT_DEFINITION: return DirectiveLocation.FRAGMENT_DEFINITION; case Kind.SCHEMA_DEFINITION: + case Kind.SCHEMA_EXTENSION: return DirectiveLocation.SCHEMA; case Kind.SCALAR_TYPE_DEFINITION: case Kind.SCALAR_TYPE_EXTENSION: