diff --git a/src/language/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql index 7e4918d2b6..e623ec4052 100644 --- a/src/language/__tests__/schema-kitchen-sink.graphql +++ b/src/language/__tests__/schema-kitchen-sink.graphql @@ -5,6 +5,11 @@ # LICENSE file in the root directory of this source tree. An additional grant # of patent rights can be found in the PATENTS file in the same directory. +schema { + query: QueryType + mutation: MutationType +} + type Foo implements Bar { one: Type two(argument: InputType!): Type diff --git a/src/language/__tests__/schema-printer.js b/src/language/__tests__/schema-printer.js index e2d6bad6f3..80b417071c 100644 --- a/src/language/__tests__/schema-printer.js +++ b/src/language/__tests__/schema-printer.js @@ -51,7 +51,12 @@ describe('Printer', () => { /* eslint-disable max-len */ expect(printed).to.equal( -`type Foo implements Bar { +`schema { + query: QueryType + mutation: MutationType +} + +type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int diff --git a/src/language/ast.js b/src/language/ast.js index 20d4271737..feda211f40 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -47,12 +47,14 @@ export type Node = Name | NamedType | ListType | NonNullType + | SchemaDefinition + | OperationTypeDefinition + | ScalarTypeDefinition | ObjectTypeDefinition | FieldDefinition | InputValueDefinition | InterfaceTypeDefinition | UnionTypeDefinition - | ScalarTypeDefinition | EnumTypeDefinition | EnumValueDefinition | InputObjectTypeDefinition @@ -77,19 +79,21 @@ export type Document = { export type Definition = OperationDefinition | FragmentDefinition - | TypeSystemDefinition + | TypeSystemDefinition // experimental non-spec addition. export type OperationDefinition = { kind: 'OperationDefinition'; loc?: ?Location; - // Note: subscription is an experimental non-spec addition. - operation: 'query' | 'mutation' | 'subscription'; + operation: OperationType; name?: ?Name; variableDefinitions?: ?Array; directives?: ?Array; selectionSet: SelectionSet; } +// Note: subscription is an experimental non-spec addition. +export type OperationType = 'query' | 'mutation' | 'subscription'; + export type VariableDefinition = { kind: 'VariableDefinition'; loc?: ?Location; @@ -256,10 +260,24 @@ export type NonNullType = { // Type System Definition -export type TypeSystemDefinition = TypeDefinition +export type TypeSystemDefinition = SchemaDefinition + | TypeDefinition | TypeExtensionDefinition | DirectiveDefinition +export type SchemaDefinition = { + kind: 'SchemaDefinition'; + loc?: ?Location; + operationTypes: Array; +} + +export type OperationTypeDefinition = { + kind: 'OperationTypeDefinition'; + loc?: ?Location; + operation: OperationType; + type: NamedType; +} + export type TypeDefinition = ScalarTypeDefinition | ObjectTypeDefinition | InterfaceTypeDefinition diff --git a/src/language/kinds.js b/src/language/kinds.js index d27681425b..372e2a51d7 100644 --- a/src/language/kinds.js +++ b/src/language/kinds.js @@ -50,6 +50,9 @@ export const NON_NULL_TYPE = 'NonNullType'; // Type System Definitions +export const SCHEMA_DEFINITION = 'SchemaDefinition'; +export const OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition'; + // Type Definitions export const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition'; diff --git a/src/language/parser.js b/src/language/parser.js index 2c726c40cf..c0bbdba762 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -19,6 +19,7 @@ import type { Document, Definition, OperationDefinition, + OperationType, VariableDefinition, SelectionSet, Selection, @@ -41,6 +42,9 @@ import type { TypeSystemDefinition, + SchemaDefinition, + OperationTypeDefinition, + ScalarTypeDefinition, ObjectTypeDefinition, FieldDefinition, @@ -86,6 +90,9 @@ import { LIST_TYPE, NON_NULL_TYPE, + SCHEMA_DEFINITION, + OPERATION_TYPE_DEFINITION, + SCALAR_TYPE_DEFINITION, OBJECT_TYPE_DEFINITION, FIELD_DEFINITION, @@ -203,6 +210,7 @@ function parseDefinition(parser: Parser): Definition { case 'fragment': return parseFragmentDefinition(parser); // Note: the Type System IDL is an experimental non-spec addition. + case 'schema': case 'scalar': case 'type': case 'interface': @@ -224,8 +232,6 @@ function parseDefinition(parser: Parser): Definition { * OperationDefinition : * - SelectionSet * - OperationType Name? VariableDefinitions? Directives? SelectionSet - * - * OperationType : one of query mutation */ function parseOperationDefinition(parser: Parser): OperationDefinition { const start = parser.token.start; @@ -240,12 +246,7 @@ function parseOperationDefinition(parser: Parser): OperationDefinition { loc: loc(parser, start) }; } - const operationToken = expect(parser, TokenKind.NAME); - const operation = - operationToken.value === 'mutation' ? 'mutation' : - operationToken.value === 'subscription' ? 'subscription' : - operationToken.value === 'query' ? 'query' : - (() => { throw unexpected(parser, operationToken); })(); + const operation = parseOperationType(parser); let name; if (peek(parser, TokenKind.NAME)) { name = parseName(parser); @@ -261,6 +262,21 @@ function parseOperationDefinition(parser: Parser): OperationDefinition { }; } +/** + * OperationType : one of query mutation subscription + */ +function parseOperationType(parser: Parser): OperationType { + const operationToken = expect(parser, TokenKind.NAME); + switch (operationToken.value) { + case 'query': return 'query'; + case 'mutation': return 'mutation'; + // Note: subscription is an experimental non-spec addition. + case 'subscription': return 'subscription'; + } + + throw unexpected(parser, operationToken); +} + /** * VariableDefinitions : ( VariableDefinition+ ) */ @@ -666,6 +682,7 @@ export function parseNamedType(parser: Parser): NamedType { function parseTypeSystemDefinition(parser: Parser): TypeSystemDefinition { if (peek(parser, TokenKind.NAME)) { switch (parser.token.value) { + case 'schema': return parseSchemaDefinition(parser); case 'scalar': return parseScalarTypeDefinition(parser); case 'type': return parseObjectTypeDefinition(parser); case 'interface': return parseInterfaceTypeDefinition(parser); @@ -680,6 +697,40 @@ function parseTypeSystemDefinition(parser: Parser): TypeSystemDefinition { throw unexpected(parser); } +/** + * SchemaDefinition : schema { OperationTypeDefinition+ } + * + * OperationTypeDefinition : OperationType : NamedType + */ +function parseSchemaDefinition(parser: Parser): SchemaDefinition { + const start = parser.token.start; + expectKeyword(parser, 'schema'); + const operationTypes = many( + parser, + TokenKind.BRACE_L, + parseOperationTypeDefinition, + TokenKind.BRACE_R + ); + return { + kind: SCHEMA_DEFINITION, + operationTypes, + loc: loc(parser, start), + }; +} + +function parseOperationTypeDefinition(parser: Parser): OperationTypeDefinition { + const start = parser.token.start; + const operation = parseOperationType(parser); + expect(parser, TokenKind.COLON); + const type = parseNamedType(parser); + return { + kind: OPERATION_TYPE_DEFINITION, + operation, + type, + loc: loc(parser, start), + }; +} + /** * ScalarTypeDefinition : scalar Name */ diff --git a/src/language/printer.js b/src/language/printer.js index c8a4c0053c..143f4fdaeb 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -94,6 +94,12 @@ const printDocASTReducer = { // Type System Definitions + SchemaDefinition: ({ operationTypes }) => + 'schema ' + block(operationTypes), + + OperationTypeDefinition: ({ operation, type }) => + operation + ': ' + type, + ScalarTypeDefinition: ({ name }) => `scalar ${name}`, diff --git a/src/language/visitor.js b/src/language/visitor.js index 3874aa2b04..34ae887b7e 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -38,16 +38,21 @@ export const QueryDocumentKeys = { ListType: [ 'type' ], NonNullType: [ 'type' ], + SchemaDefinition: [ 'operationTypes' ], + OperationTypeDefinition: [ 'type' ], + + ScalarTypeDefinition: [ 'name' ], ObjectTypeDefinition: [ 'name', 'interfaces', 'fields' ], FieldDefinition: [ 'name', 'arguments', 'type' ], InputValueDefinition: [ 'name', 'type', 'defaultValue' ], InterfaceTypeDefinition: [ 'name', 'fields' ], UnionTypeDefinition: [ 'name', 'types' ], - ScalarTypeDefinition: [ 'name' ], EnumTypeDefinition: [ 'name', 'values' ], EnumValueDefinition: [ 'name' ], InputObjectTypeDefinition: [ 'name', 'fields' ], + TypeExtensionDefinition: [ 'definition' ], + DirectiveDefinition: [ 'name', 'arguments', 'locations' ], }; diff --git a/src/utilities/__tests__/buildASTSchema.js b/src/utilities/__tests__/buildASTSchema.js index 06e85a3104..71d73beb6f 100644 --- a/src/utilities/__tests__/buildASTSchema.js +++ b/src/utilities/__tests__/buildASTSchema.js @@ -20,15 +20,19 @@ import { buildASTSchema } from '../buildASTSchema'; * into an in-memory GraphQLSchema, and then finally * printing that GraphQL into the DSL */ -function cycleOutput(body, queryType, mutationType, subscriptionType) { +function cycleOutput(body) { const ast = parse(body); - const schema = buildASTSchema(ast, queryType, mutationType, subscriptionType); + const schema = buildASTSchema(ast); return '\n' + printSchema(schema); } -describe('Schema Materializer', () => { +describe('Schema Builder', () => { it('Simple type', () => { const body = ` +schema { + query: HelloScalars +} + type HelloScalars { str: String int: Int @@ -37,24 +41,32 @@ type HelloScalars { bool: Boolean } `; - const output = cycleOutput(body, 'HelloScalars'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('With directives', () => { const body = ` +schema { + query: Hello +} + directive @foo(arg: Int) on FIELD type Hello { str: String } `; - const output = cycleOutput(body, 'Hello'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Type modifiers', () => { const body = ` +schema { + query: HelloScalars +} + type HelloScalars { nonNullStr: String! listOfStrs: [String] @@ -63,24 +75,32 @@ type HelloScalars { nonNullListOfNonNullStrs: [String!]! } `; - const output = cycleOutput(body, 'HelloScalars'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Recursive type', () => { const body = ` +schema { + query: Recurse +} + type Recurse { str: String recurse: Recurse } `; - const output = cycleOutput(body, 'Recurse'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Two types circular', () => { const body = ` +schema { + query: TypeOne +} + type TypeOne { str: String typeTwo: TypeTwo @@ -91,12 +111,16 @@ type TypeTwo { typeOne: TypeOne } `; - const output = cycleOutput(body, 'TypeOne'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Single argument field', () => { const body = ` +schema { + query: Hello +} + type Hello { str(int: Int): String floatToStr(float: Float): String @@ -105,12 +129,16 @@ type Hello { strToStr(bool: String): String } `; - const output = cycleOutput(body, 'Hello'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Simple type with multiple arguments', () => { const body = ` +schema { + query: Hello +} + type Hello { str(int: Int, bool: Boolean): String } @@ -121,7 +149,11 @@ type Hello { it('Simple type with interface', () => { const body = ` -type HelloInterface implements WorldInterface { +schema { + query: Hello +} + +type Hello implements WorldInterface { str: String } @@ -129,12 +161,16 @@ interface WorldInterface { str: String } `; - const output = cycleOutput(body, 'HelloInterface'); + const output = cycleOutput(body, 'Hello'); expect(output).to.equal(body); }); it('Simple output enum', () => { const body = ` +schema { + query: OutputEnumRoot +} + enum Hello { WORLD } @@ -143,12 +179,16 @@ type OutputEnumRoot { hello: Hello } `; - const output = cycleOutput(body, 'OutputEnumRoot'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Simple input enum', () => { const body = ` +schema { + query: InputEnumRoot +} + enum Hello { WORLD } @@ -157,12 +197,16 @@ type InputEnumRoot { str(hello: Hello): String } `; - const output = cycleOutput(body, 'InputEnumRoot'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Multiple value enum', () => { const body = ` +schema { + query: OutputEnumRoot +} + enum Hello { WO RLD @@ -172,12 +216,16 @@ type OutputEnumRoot { hello: Hello } `; - const output = cycleOutput(body, 'OutputEnumRoot'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Simple Union', () => { const body = ` +schema { + query: Root +} + union Hello = World type Root { @@ -188,12 +236,16 @@ type World { str: String } `; - const output = cycleOutput(body, 'Root'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Multiple Union', () => { const body = ` +schema { + query: Root +} + union Hello = WorldOne | WorldTwo type Root { @@ -208,12 +260,16 @@ type WorldTwo { str: String } `; - const output = cycleOutput(body, 'Root'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Custom Scalar', () => { const body = ` +schema { + query: Root +} + scalar CustomScalar type Root { @@ -221,12 +277,16 @@ type Root { } `; - const output = cycleOutput(body, 'Root'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Input Object', async() => { const body = ` +schema { + query: Root +} + input Input { int: Int } @@ -236,22 +296,31 @@ type Root { } `; - const output = cycleOutput(body, 'Root'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Simple argument field with default', () => { const body = ` +schema { + query: Hello +} + type Hello { str(int: Int = 2): String } `; - const output = cycleOutput(body, 'Hello'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Simple type with mutation', () => { const body = ` +schema { + query: HelloScalars + mutation: Mutation +} + type HelloScalars { str: String int: Int @@ -262,12 +331,17 @@ type Mutation { addHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars } `; - const output = cycleOutput(body, 'HelloScalars', 'Mutation'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Simple type with subscription', () => { const body = ` +schema { + query: HelloScalars + subscription: Subscription +} + type HelloScalars { str: String int: Int @@ -278,12 +352,16 @@ type Subscription { subscribeHelloScalars(str: String, int: Int, bool: Boolean): HelloScalars } `; - const output = cycleOutput(body, 'HelloScalars', null, 'Subscription'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Unreferenced type implementing referenced interface', () => { const body = ` +schema { + query: Query +} + type Concrete implements Iface { key: String } @@ -296,12 +374,16 @@ type Query { iface: Iface } `; - const output = cycleOutput(body, 'Query'); + const output = cycleOutput(body); expect(output).to.equal(body); }); it('Unreferenced type implementing referenced union', () => { const body = ` +schema { + query: Query +} + type Concrete { key: String } @@ -312,66 +394,139 @@ type Query { union Union = Concrete `; - const output = cycleOutput(body, 'Query'); + const output = cycleOutput(body); expect(output).to.equal(body); }); }); -describe('Schema Parser Failures', () => { +describe('Failures', () => { + + it('Requires a schema definition', () => { + const body = ` +type Hello { + bar: Bar +} +`; + const doc = parse(body); + expect(() => buildASTSchema(doc)) + .to.throw('Must provide a schema definition.'); + }); + + it('Allows only a single schema definition', () => { + const body = ` +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('Requires a query type', () => { + const body = ` +schema { + mutation: Hello +} + +type Hello { + bar: Bar +} +`; + const doc = parse(body); + expect(() => buildASTSchema(doc)) + .to.throw('Must provide schema definition with query type.'); + }); + it('Unknown type referenced', () => { const body = ` +schema { + query: Hello +} + type Hello { bar: Bar } `; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Hello')) - .to.throw('Type Bar not found in document'); + expect(() => buildASTSchema(doc)) + .to.throw('Type "Bar" not found in document.'); }); it('Unknown type in interface list', () => { const body = ` +schema { + query: Hello +} + type Hello implements Bar { } `; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Hello')) - .to.throw('Type Bar not found in document'); + expect(() => buildASTSchema(doc)) + .to.throw('Type "Bar" not found in document.'); }); it('Unknown type in union list', () => { const body = ` +schema { + query: Hello +} + union TestUnion = Bar type Hello { testUnion: TestUnion } `; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Hello')) - .to.throw('Type Bar not found in document'); + expect(() => buildASTSchema(doc)) + .to.throw('Type "Bar" not found in document.'); }); it('Unknown query type', () => { const body = ` +schema { + query: Wat +} + type Hello { str: String } `; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Wat')) - .to.throw('Specified query type Wat not found in document'); + expect(() => buildASTSchema(doc)) + .to.throw('Specified query type "Wat" not found in document.'); }); it('Unknown mutation type', () => { const body = ` +schema { + query: Hello + mutation: Wat +} + type Hello { str: String } `; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Hello', 'Wat')) - .to.throw('Specified mutation type Wat not found in document'); + expect(() => buildASTSchema(doc)) + .to.throw('Specified mutation type "Wat" not found in document.'); }); it('Unknown subscription type', () => { const body = ` +schema { + query: Hello + mutation: Wat + subscription: Awesome +} + type Hello { str: String } @@ -381,22 +536,34 @@ type Wat { } `; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Hello', 'Wat', 'Awesome')) - .to.throw('Specified subscription type Awesome not found in document'); + expect(() => buildASTSchema(doc)) + .to.throw('Specified subscription type "Awesome" not found in document.'); }); - it('Rejects query names', () => { - const body = 'query Foo { field }'; + it('Does not consider operation names', () => { + const body = ` +schema { + query: Foo +} + +query Foo { field } +`; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Foo')) - .to.throw('Specified query type Foo not found in document.'); + expect(() => buildASTSchema(doc)) + .to.throw('Specified query type "Foo" not found in document.'); }); - it('Rejects fragment names', () => { - const body = 'fragment Foo on Type { field }'; + it('Does not consider fragment names', () => { + const body = ` +schema { + query: Foo +} + +fragment Foo on Type { field } +`; const doc = parse(body); - expect(() => buildASTSchema(doc, 'Foo')) - .to.throw('Specified query type Foo not found in document.'); + expect(() => buildASTSchema(doc)) + .to.throw('Specified query type "Foo" not found in document.'); }); }); diff --git a/src/utilities/__tests__/extendSchema.js b/src/utilities/__tests__/extendSchema.js index 0d78a76940..a13da3829d 100644 --- a/src/utilities/__tests__/extendSchema.js +++ b/src/utilities/__tests__/extendSchema.js @@ -133,7 +133,11 @@ describe('extendSchema', () => { expect(extendedSchema).to.not.equal(testSchema); expect(printSchema(testSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Bar implements SomeInterface { +`schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -182,7 +186,11 @@ union SomeUnion = Foo | Biz expect(extendedSchema).to.not.equal(testSchema); expect(printSchema(testSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Bar implements SomeInterface { +`schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -232,7 +240,11 @@ union SomeUnion = Foo | Biz expect(extendedSchema).to.not.equal(testSchema); expect(printSchema(testSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Bar implements SomeInterface { +`schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -302,7 +314,11 @@ union SomeUnion = Foo | Biz expect(extendedSchema).to.not.equal(testSchema); expect(printSchema(testSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Bar implements SomeInterface { +`schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -375,7 +391,11 @@ union SomeUnion = Foo | Biz expect(extendedSchema).to.not.equal(testSchema); expect(printSchema(testSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Bar implements SomeInterface { +`schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -437,7 +457,11 @@ union SomeUnion = Foo | Biz expect(extendedSchema).to.not.equal(testSchema); expect(printSchema(testSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Bar implements SomeInterface { +`schema { + query: Query +} + +type Bar implements SomeInterface { name: String some: SomeInterface foo: Foo @@ -517,7 +541,13 @@ union SomeUnion = Foo | Biz expect(extendedSchema).to.not.equal(mutationSchema); expect(printSchema(mutationSchema)).to.equal(originalPrint); expect(printSchema(extendedSchema)).to.equal( -`type Mutation { +`schema { + query: Query + mutation: Mutation + subscription: Subscription +} + +type Mutation { mutationField: String newMutationField: Int } diff --git a/src/utilities/__tests__/schemaPrinter.js b/src/utilities/__tests__/schemaPrinter.js index de64fbd1ff..c6fce71f40 100644 --- a/src/utilities/__tests__/schemaPrinter.js +++ b/src/utilities/__tests__/schemaPrinter.js @@ -52,6 +52,10 @@ describe('Type System Printer', () => { type: GraphQLString }); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField: String } @@ -64,6 +68,10 @@ type Root { type: listOf(GraphQLString) }); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField: [String] } @@ -76,6 +84,10 @@ type Root { type: nonNull(GraphQLString) }); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField: String! } @@ -88,6 +100,10 @@ type Root { type: nonNull(listOf(GraphQLString)) }); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField: [String]! } @@ -100,6 +116,10 @@ type Root { type: listOf(nonNull(GraphQLString)) }); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField: [String!] } @@ -112,6 +132,10 @@ type Root { type: nonNull(listOf(nonNull(GraphQLString))) }); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField: [String!]! } @@ -133,6 +157,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + type Foo { str: String } @@ -152,6 +180,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int): String } @@ -167,6 +199,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int = 2): String } @@ -182,6 +218,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int!): String } @@ -200,6 +240,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int, argTwo: String): String } @@ -219,6 +263,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String } @@ -238,6 +286,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String } @@ -257,6 +309,10 @@ type Root { } ); expect(output).to.equal(` +schema { + query: Root +} + type Root { singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String } @@ -285,6 +341,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + type Bar implements Foo { str: String } @@ -330,6 +390,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + interface Baaz { int: Int } @@ -388,6 +452,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + type Bar { str: String } @@ -429,6 +497,10 @@ union SingleUnion = Foo const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + input InputType { int: Int } @@ -457,6 +529,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + scalar Odd type Root { @@ -486,6 +562,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = printForTest(Schema); expect(output).to.equal(` +schema { + query: Root +} + enum RGB { RED GREEN @@ -508,6 +588,10 @@ type Root { const Schema = new GraphQLSchema({ query: Root }); const output = '\n' + printIntrospectionSchema(Schema); const introspectionSchema = ` +schema { + query: Root +} + directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 0d309c4a9a..c2bee49524 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -19,11 +19,13 @@ import { } from '../language/kinds'; import { + DOCUMENT, + SCHEMA_DEFINITION, + SCALAR_TYPE_DEFINITION, OBJECT_TYPE_DEFINITION, INTERFACE_TYPE_DEFINITION, ENUM_TYPE_DEFINITION, UNION_TYPE_DEFINITION, - SCALAR_TYPE_DEFINITION, INPUT_OBJECT_TYPE_DEFINITION, DIRECTIVE_DEFINITION, } from '../language/kinds'; @@ -32,12 +34,13 @@ import type { Document, Type, NamedType, + SchemaDefinition, TypeDefinition, + ScalarTypeDefinition, ObjectTypeDefinition, InputValueDefinition, InterfaceTypeDefinition, UnionTypeDefinition, - ScalarTypeDefinition, EnumTypeDefinition, InputObjectTypeDefinition, DirectiveDefinition, @@ -45,10 +48,10 @@ import type { import { GraphQLSchema, + GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, - GraphQLScalarType, GraphQLEnumType, GraphQLInputObjectType, GraphQLString, @@ -104,30 +107,29 @@ function getNamedTypeAST(typeAST: Type): NamedType { * they are not particularly useful for non-introspection queries * since they have no resolve methods. */ -export function buildASTSchema( - ast: Document, - queryTypeName: string, - mutationTypeName: ?string, - subscriptionTypeName: ?string -): GraphQLSchema { - if (!ast) { - throw new Error('must pass in ast'); +export function buildASTSchema(ast: Document): GraphQLSchema { + if (!ast || ast.kind !== DOCUMENT) { + throw new Error('Must provide a document ast.'); } - if (!queryTypeName) { - throw new Error('must pass in query type'); - } + let schemaDef: ?SchemaDefinition; const typeDefs: Array = []; const directiveDefs: Array = []; for (let i = 0; i < ast.definitions.length; i++) { const d = ast.definitions[i]; switch (d.kind) { + case SCHEMA_DEFINITION: + if (schemaDef) { + throw new Error('Must provide only one schema definition.'); + } + schemaDef = d; + break; + case SCALAR_TYPE_DEFINITION: case OBJECT_TYPE_DEFINITION: case INTERFACE_TYPE_DEFINITION: case ENUM_TYPE_DEFINITION: case UNION_TYPE_DEFINITION: - case SCALAR_TYPE_DEFINITION: case INPUT_OBJECT_TYPE_DEFINITION: typeDefs.push(d); break; @@ -137,25 +139,47 @@ export function buildASTSchema( } } + if (!schemaDef) { + throw new Error('Must provide a schema definition.'); + } + + let queryTypeName; + let mutationTypeName; + let subscriptionTypeName; + schemaDef.operationTypes.forEach(operationType => { + const typeName = operationType.type.name.value; + if (operationType.operation === 'query') { + queryTypeName = typeName; + } else if (operationType.operation === 'mutation') { + mutationTypeName = typeName; + } else if (operationType.operation === 'subscription') { + subscriptionTypeName = typeName; + } + }); + + if (!queryTypeName) { + throw new Error('Must provide schema definition with query type.'); + } + const astMap: {[name: string]: TypeDefinition} = keyMap(typeDefs, d => d.name.value); if (!astMap[queryTypeName]) { throw new Error( - `Specified query type ${queryTypeName} not found in document.` + `Specified query type "${queryTypeName}" not found in document.` ); } if (mutationTypeName && !astMap[mutationTypeName]) { throw new Error( - `Specified mutation type ${mutationTypeName} not found in document.` + `Specified mutation type "${mutationTypeName}" not found in document.` ); } if (subscriptionTypeName && !astMap[subscriptionTypeName]) { throw new Error( - `Specified subscription type ${ - subscriptionTypeName} not found in document.` + `Specified subscription type "${ + subscriptionTypeName}" not found in document.` ); } @@ -208,12 +232,12 @@ export function buildASTSchema( } if (!astMap[typeName]) { - throw new Error(`Type ${typeName} not found in document`); + throw new Error(`Type "${typeName}" not found in document.`); } const innerTypeDef = makeSchemaDef(astMap[typeName]); if (!innerTypeDef) { - throw new Error(`Nothing constructed for ${typeName}`); + throw new Error(`Nothing constructed for "${typeName}".`); } innerTypeMap[typeName] = innerTypeDef; return innerTypeDef; @@ -237,7 +261,7 @@ export function buildASTSchema( case INPUT_OBJECT_TYPE_DEFINITION: return makeInputObjectDef(def); default: - throw new Error(`${def.kind} not supported`); + throw new Error(`Type kind "${def.kind}" not supported.`); } } diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index 3c36eddcf8..38e51f4dc0 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -66,11 +66,33 @@ function printFilteredSchema( .filter(typeFilter) .sort((name1, name2) => name1.localeCompare(name2)) .map(typeName => typeMap[typeName]); - return directives.map(printDirective).concat( + return [ printSchemaDefinition(schema) ].concat( + directives.map(printDirective), types.map(printType) ).join('\n\n') + '\n'; } +function printSchemaDefinition(schema: GraphQLSchema): string { + const operationTypes = []; + + const queryType = schema.getQueryType(); + if (queryType) { + operationTypes.push(` query: ${queryType}`); + } + + const mutationType = schema.getMutationType(); + if (mutationType) { + operationTypes.push(` mutation: ${mutationType}`); + } + + const subscriptionType = schema.getSubscriptionType(); + if (subscriptionType) { + operationTypes.push(` subscription: ${subscriptionType}`); + } + + return `schema {\n${operationTypes.join('\n')}\n}`; +} + function printType(type: GraphQLType): string { if (type instanceof GraphQLScalarType) { return printScalar(type);