diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index 224efc73c6..d041f35db6 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -809,26 +809,6 @@ describe('Schema Builder', () => { }); describe('Failures', () => { - it('Allows only a single schema definition', () => { - const body = dedent` - schema { - query: Hello - } - - schema { - query: Hello - } - - type Hello { - bar: Bar - } - `; - const doc = parse(body); - expect(() => buildASTSchema(doc)).to.throw( - 'Must provide only one schema definition.', - ); - }); - it('Allows only a single query type', () => { const body = dedent` schema { @@ -837,7 +817,7 @@ describe('Failures', () => { } type Hello { - bar: Bar + bar: String } type Yellow { @@ -859,7 +839,7 @@ describe('Failures', () => { } type Hello { - bar: Bar + bar: String } type Yellow { @@ -881,7 +861,7 @@ describe('Failures', () => { } type Hello { - bar: Bar + bar: String } type Yellow { diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index 1fcde93745..8c433fa9be 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -1288,21 +1288,6 @@ describe('extendSchema', () => { expect(schema.getMutationType()).to.equal(null); }); - it('does not allow overriding schema within an extension', () => { - const sdl = ` - schema { - mutation: Mutation - } - - type Mutation { - doSomething: String - } - `; - expect(() => extendTestSchema(sdl)).to.throw( - 'Cannot define a new schema within a schema extension.', - ); - }); - it('adds schema definition missing in the original schema', () => { let schema = new GraphQLSchema({ directives: [FooDirective], diff --git a/src/validation/__tests__/KnownDirectives-test.js b/src/validation/__tests__/KnownDirectives-test.js index 6e0787db08..6e0c046842 100644 --- a/src/validation/__tests__/KnownDirectives-test.js +++ b/src/validation/__tests__/KnownDirectives-test.js @@ -191,46 +191,92 @@ describe('Validate: Known directives', () => { `).to.deep.equal([]); }); - it('with well placed directives', () => { + it('with directive defined in schema extension', () => { + const schema = buildSchema(` + type Query { + foo: String + } + `); expectSDLErrors( ` - type MyObj implements MyInterface @onObject { - myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition - } + directive @test on OBJECT - extend type MyObj @onObject + extend type Query @test + `, + schema, + ).to.deep.equal([]); + }); - scalar MyScalar @onScalar + it('with directive used in schema extension', () => { + const schema = buildSchema(` + directive @test on OBJECT - extend scalar MyScalar @onScalar + type Query { + foo: String + } + `); + expectSDLErrors( + ` + extend type Query @test + `, + schema, + ).to.deep.equal([]); + }); - interface MyInterface @onInterface { - myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition + it('with unknown directive in schema extension', () => { + const schema = buildSchema(` + type Query { + foo: String } + `); + expectSDLErrors( + ` + extend type Query @unknown + `, + schema, + ).to.deep.equal([unknownDirective('unknown', 2, 29)]); + }); - extend interface MyInterface @onInterface + it('with well placed directives', () => { + expectSDLErrors( + ` + type MyObj implements MyInterface @onObject { + myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition + } - union MyUnion @onUnion = MyObj | Other + extend type MyObj @onObject - extend union MyUnion @onUnion + scalar MyScalar @onScalar - enum MyEnum @onEnum { - MY_VALUE @onEnumValue - } + extend scalar MyScalar @onScalar - extend enum MyEnum @onEnum + interface MyInterface @onInterface { + myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition + } - input MyInput @onInputObject { - myField: Int @onInputFieldDefinition - } + extend interface MyInterface @onInterface - extend input MyInput @onInputObject + union MyUnion @onUnion = MyObj | Other - schema @onSchema { - query: MyQuery - } + extend union MyUnion @onUnion + + enum MyEnum @onEnum { + MY_VALUE @onEnumValue + } + + extend enum MyEnum @onEnum + + input MyInput @onInputObject { + myField: Int @onInputFieldDefinition + } + + extend input MyInput @onInputObject + + schema @onSchema { + query: MyQuery + } - extend schema @onSchema + extend schema @onSchema `, schemaWithSDLDirectives, ).to.deep.equal([]); @@ -239,63 +285,63 @@ describe('Validate: Known directives', () => { it('with misplaced directives', () => { expectSDLErrors( ` - type MyObj implements MyInterface @onInterface { - myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition - } + type MyObj implements MyInterface @onInterface { + myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition + } - scalar MyScalar @onEnum + scalar MyScalar @onEnum - interface MyInterface @onObject { - myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition - } + interface MyInterface @onObject { + myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition + } - union MyUnion @onEnumValue = MyObj | Other + union MyUnion @onEnumValue = MyObj | Other - enum MyEnum @onScalar { - MY_VALUE @onUnion - } + enum MyEnum @onScalar { + MY_VALUE @onUnion + } - input MyInput @onEnum { - myField: Int @onArgumentDefinition - } + input MyInput @onEnum { + myField: Int @onArgumentDefinition + } - schema @onObject { - query: MyQuery - } + schema @onObject { + query: MyQuery + } - extend schema @onObject + extend schema @onObject `, schemaWithSDLDirectives, ).to.deep.equal([ - misplacedDirective('onInterface', 'OBJECT', 2, 43), + misplacedDirective('onInterface', 'OBJECT', 2, 45), misplacedDirective( 'onInputFieldDefinition', 'ARGUMENT_DEFINITION', 3, - 30, + 32, ), - misplacedDirective('onInputFieldDefinition', 'FIELD_DEFINITION', 3, 63), - misplacedDirective('onEnum', 'SCALAR', 6, 25), - misplacedDirective('onObject', 'INTERFACE', 8, 31), + misplacedDirective('onInputFieldDefinition', 'FIELD_DEFINITION', 3, 65), + misplacedDirective('onEnum', 'SCALAR', 6, 27), + misplacedDirective('onObject', 'INTERFACE', 8, 33), misplacedDirective( 'onInputFieldDefinition', 'ARGUMENT_DEFINITION', 9, - 30, + 32, ), - 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('onInputFieldDefinition', 'FIELD_DEFINITION', 9, 65), + misplacedDirective('onEnumValue', 'UNION', 12, 25), + misplacedDirective('onScalar', 'ENUM', 14, 23), + misplacedDirective('onUnion', 'ENUM_VALUE', 15, 22), + misplacedDirective('onEnum', 'INPUT_OBJECT', 18, 25), misplacedDirective( 'onArgumentDefinition', 'INPUT_FIELD_DEFINITION', 19, - 24, + 26, ), - misplacedDirective('onObject', 'SCHEMA', 22, 16), - misplacedDirective('onObject', 'SCHEMA', 26, 23), + misplacedDirective('onObject', 'SCHEMA', 22, 18), + misplacedDirective('onObject', 'SCHEMA', 26, 25), ]); }); }); diff --git a/src/validation/__tests__/LoneSchemaDefinition-test.js b/src/validation/__tests__/LoneSchemaDefinition-test.js new file mode 100644 index 0000000000..8b144672fd --- /dev/null +++ b/src/validation/__tests__/LoneSchemaDefinition-test.js @@ -0,0 +1,159 @@ +/** + * 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. + */ + +import { describe, it } from 'mocha'; +import { expectSDLErrorsFromRule } from './harness'; +import { + LoneSchemaDefinition, + schemaDefinitionNotAloneMessage, + canNotDefineSchemaWithinExtensionMessage, +} from '../rules/LoneSchemaDefinition'; +import { buildSchema } from '../../utilities'; + +const expectSDLErrors = expectSDLErrorsFromRule.bind( + undefined, + LoneSchemaDefinition, +); + +function schemaDefinitionNotAlone(line, column) { + return { + message: schemaDefinitionNotAloneMessage(), + locations: [{ line, column }], + }; +} + +function canNotDefineSchemaWithinExtension(line, column) { + return { + message: canNotDefineSchemaWithinExtensionMessage(), + locations: [{ line, column }], + }; +} + +describe('Validate: Schema definition should be alone', () => { + it('no schema', () => { + expectSDLErrors(` + type Query { + foo: String + } + `).to.deep.equal([]); + }); + + it('one schema definition', () => { + expectSDLErrors(` + schema { + query: Foo + } + + type Foo { + foo: String + } + `).to.deep.equal([]); + }); + + it('multiple schema definitions', () => { + expectSDLErrors(` + schema { + query: Foo + } + + type Foo { + foo: String + } + + schema { + mutation: Foo + } + + schema { + subscription: Foo + } + `).to.deep.equal([ + schemaDefinitionNotAlone(10, 7), + schemaDefinitionNotAlone(14, 7), + ]); + }); + + it('define schema in schema extension', () => { + const schema = buildSchema(` + type Foo { + foo: String + } + `); + + expectSDLErrors( + ` + schema { + query: Foo + } + `, + schema, + ).to.deep.equal([]); + }); + + it('redefine schema in schema extension', () => { + const schema = buildSchema(` + schema { + query: Foo + } + + type Foo { + foo: String + } + `); + + expectSDLErrors( + ` + schema { + mutation: Foo + } + `, + schema, + ).to.deep.equal([canNotDefineSchemaWithinExtension(2, 9)]); + }); + + it('redefine implicit schema in schema extension', () => { + const schema = buildSchema(` + type Query { + fooField: Foo + } + + type Foo { + foo: String + } + `); + + expectSDLErrors( + ` + schema { + mutation: Foo + } + `, + schema, + ).to.deep.equal([canNotDefineSchemaWithinExtension(2, 9)]); + }); + + it('extend schema in schema extension', () => { + const schema = buildSchema(` + type Query { + fooField: Foo + } + + type Foo { + foo: String + } + `); + + expectSDLErrors( + ` + extend schema { + mutation: Foo + } + `, + schema, + ).to.deep.equal([]); + }); +}); diff --git a/src/validation/rules/LoneSchemaDefinition.js b/src/validation/rules/LoneSchemaDefinition.js index e76f2d94be..c63871c5a1 100644 --- a/src/validation/rules/LoneSchemaDefinition.js +++ b/src/validation/rules/LoneSchemaDefinition.js @@ -15,7 +15,7 @@ export function schemaDefinitionNotAloneMessage(): string { return 'Must provide only one schema definition.'; } -export function canNotDefineSchemaWithinExtension(): string { +export function canNotDefineSchemaWithinExtensionMessage(): string { return 'Cannot define a new schema within a schema extension.'; } @@ -35,25 +35,22 @@ export function LoneSchemaDefinition( oldSchema.getMutationType() || oldSchema.getSubscriptionType()); - const schemaNodes = []; + let schemaDefinitionsCount = 0; return { SchemaDefinition(node) { if (alreadyDefined) { context.reportError( - new GraphQLError(canNotDefineSchemaWithinExtension(), [node]), + new GraphQLError(canNotDefineSchemaWithinExtensionMessage(), node), ); return; } - schemaNodes.push(node); - }, - Document: { - leave() { - if (schemaNodes.length > 1) { - context.reportError( - new GraphQLError(schemaDefinitionNotAloneMessage(), schemaNodes), - ); - } - }, + + if (schemaDefinitionsCount > 0) { + context.reportError( + new GraphQLError(schemaDefinitionNotAloneMessage(), node), + ); + } + ++schemaDefinitionsCount; }, }; }