Skip to content

Commit f24cc3e

Browse files
committed
Add support for adding description to schema
Implements graphql/graphql-spec#466
1 parent d680d07 commit f24cc3e

23 files changed

+139
-9
lines changed

src/__fixtures__/schema-kitchen-sink.graphql

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""This is a description of the schema as a whole."""
12
schema {
23
query: QueryType
34
mutation: MutationType

src/language/__tests__/schema-parser-test.js

+19
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,25 @@ describe('Schema Parser', () => {
141141
);
142142
});
143143

144+
it('parses schema with description string', () => {
145+
const doc = parse(dedent`
146+
"Description"
147+
schema {
148+
query: Foo
149+
}
150+
`);
151+
152+
expect(toJSONDeep(doc)).to.nested.deep.property(
153+
'definitions[0].description',
154+
{
155+
kind: 'StringValue',
156+
value: 'Description',
157+
block: false,
158+
loc: { start: 0, end: 13 },
159+
},
160+
);
161+
});
162+
144163
it('Description followed by something other than type system definition throws', () => {
145164
expectSyntaxError('"Description" 1').to.deep.equal({
146165
message: 'Syntax Error: Unexpected Int "1".',

src/language/__tests__/schema-printer-test.js

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe('Printer: SDL document', () => {
3838
const printed = print(parse(kitchenSinkSDL));
3939

4040
expect(printed).to.equal(dedent`
41+
"""This is a description of the schema as a whole."""
4142
schema {
4243
query: QueryType
4344
mutation: MutationType

src/language/ast.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ export type TypeSystemDefinitionNode =
405405
export interface SchemaDefinitionNode {
406406
readonly kind: 'SchemaDefinition';
407407
readonly loc?: Location;
408+
readonly description?: StringValueNode;
408409
readonly directives?: ReadonlyArray<DirectiveNode>;
409410
readonly operationTypes: ReadonlyArray<OperationTypeDefinitionNode>;
410411
}

src/language/ast.js

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ export type TypeSystemDefinitionNode =
441441
export type SchemaDefinitionNode = {|
442442
+kind: 'SchemaDefinition',
443443
+loc?: Location,
444+
+description?: StringValueNode,
444445
+directives?: $ReadOnlyArray<DirectiveNode>,
445446
+operationTypes: $ReadOnlyArray<OperationTypeDefinitionNode>,
446447
|};

src/language/parser.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -772,10 +772,11 @@ class Parser {
772772
}
773773

774774
/**
775-
* SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ }
775+
* SchemaDefinition : Description? schema Directives[Const]? { OperationTypeDefinition+ }
776776
*/
777777
parseSchemaDefinition(): SchemaDefinitionNode {
778778
const start = this._lexer.token;
779+
const description = this.parseDescription();
779780
this.expectKeyword('schema');
780781
const directives = this.parseDirectives(true);
781782
const operationTypes = this.many(
@@ -785,6 +786,7 @@ class Parser {
785786
);
786787
return {
787788
kind: Kind.SCHEMA_DEFINITION,
789+
description,
788790
directives,
789791
operationTypes,
790792
loc: this.loc(start),

src/language/printer.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ const printDocASTReducer: any = {
106106

107107
// Type System Definitions
108108

109-
SchemaDefinition: ({ directives, operationTypes }) =>
109+
SchemaDefinition: addDescription(({ directives, operationTypes }) =>
110110
join(['schema', join(directives, ' '), block(operationTypes)], ' '),
111+
),
111112

112113
OperationTypeDefinition: ({ operation, type }) => operation + ': ' + type,
113114

src/language/visitor.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const QueryDocumentKeys: {
101101
ListType: ['type'];
102102
NonNullType: ['type'];
103103

104-
SchemaDefinition: ['directives', 'operationTypes'];
104+
SchemaDefinition: ['description', 'directives', 'operationTypes'];
105105
OperationTypeDefinition: ['type'];
106106

107107
ScalarTypeDefinition: ['description', 'name', 'directives'];

src/language/visitor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const QueryDocumentKeys: VisitorKeyMap<ASTKindToNode> = {
9292
ListType: ['type'],
9393
NonNullType: ['type'],
9494

95-
SchemaDefinition: ['directives', 'operationTypes'],
95+
SchemaDefinition: ['description', 'directives', 'operationTypes'],
9696
OperationTypeDefinition: ['type'],
9797

9898
ScalarTypeDefinition: ['description', 'name', 'directives'],

src/type/__tests__/introspection-test.js

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
describe('Introspection', () => {
2121
it('executes an introspection query', () => {
2222
const schema = new GraphQLSchema({
23+
description: 'Sample schema',
2324
query: new GraphQLObjectType({
2425
name: 'QueryRoot',
2526
fields: {
@@ -85,6 +86,17 @@ describe('Introspection', () => {
8586
kind: 'OBJECT',
8687
name: '__Schema',
8788
fields: [
89+
{
90+
name: 'description',
91+
args: [],
92+
type: {
93+
kind: 'SCALAR',
94+
name: 'String',
95+
ofType: null,
96+
},
97+
isDeprecated: false,
98+
deprecationReason: null,
99+
},
88100
{
89101
name: 'types',
90102
args: [],
@@ -1304,6 +1316,10 @@ describe('Introspection', () => {
13041316
description:
13051317
'A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.',
13061318
fields: [
1319+
{
1320+
name: 'description',
1321+
description: null,
1322+
},
13071323
{
13081324
name: 'types',
13091325
description: 'A list of all types supported by this server.',

src/type/__tests__/schema-test.js

+8
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,20 @@ describe('Type System: Schema', () => {
8686
});
8787

8888
const schema = new GraphQLSchema({
89+
description: 'Sample schema',
8990
query: BlogQuery,
9091
mutation: BlogMutation,
9192
subscription: BlogSubscription,
9293
});
9394

9495
expect(printSchema(schema)).to.equal(dedent`
96+
"""Sample schema"""
97+
schema {
98+
query: Query
99+
mutation: Mutation
100+
subscription: Subscription
101+
}
102+
95103
type Query {
96104
article(id: String): Article
97105
feed: [Article]

src/type/introspection.js

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export const __Schema = new GraphQLObjectType({
4343
'A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.',
4444
fields: () =>
4545
({
46+
description: {
47+
type: GraphQLString,
48+
resolve: schema => schema.description,
49+
},
4650
types: {
4751
description: 'A list of all types supported by this server.',
4852
type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__Type))),

src/type/schema.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function assertSchema(schema: any): GraphQLSchema;
4343
*
4444
*/
4545
export class GraphQLSchema {
46+
description: Maybe<string>;
4647
extensions: Maybe<Readonly<Record<string, any>>>;
4748
astNode: Maybe<SchemaDefinitionNode>;
4849
extensionASTNodes: Maybe<ReadonlyArray<SchemaExtensionNode>>;
@@ -104,6 +105,7 @@ export interface GraphQLSchemaValidationOptions {
104105
}
105106

106107
export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions {
108+
description?: Maybe<string>;
107109
query: Maybe<GraphQLObjectType>;
108110
mutation?: Maybe<GraphQLObjectType>;
109111
subscription?: Maybe<GraphQLObjectType>;
@@ -118,6 +120,7 @@ export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions {
118120
* @internal
119121
*/
120122
export interface GraphQLSchemaNormalizedConfig extends GraphQLSchemaConfig {
123+
description: Maybe<string>;
121124
types: Array<GraphQLNamedType>;
122125
directives: Array<GraphQLDirective>;
123126
extensions: Maybe<Readonly<Record<string, any>>>;

src/type/schema.js

+5
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export function assertSchema(schema: mixed): GraphQLSchema {
122122
*
123123
*/
124124
export class GraphQLSchema {
125+
description: ?string;
125126
extensions: ?ReadOnlyObjMap<mixed>;
126127
astNode: ?SchemaDefinitionNode;
127128
extensionASTNodes: ?$ReadOnlyArray<SchemaExtensionNode>;
@@ -157,6 +158,7 @@ export class GraphQLSchema {
157158
`${inspect(config.directives)}.`,
158159
);
159160

161+
this.description = config.description;
160162
this.extensions = config.extensions && toObjMap(config.extensions);
161163
this.astNode = config.astNode;
162164
this.extensionASTNodes = config.extensionASTNodes;
@@ -335,6 +337,7 @@ export class GraphQLSchema {
335337

336338
toConfig(): GraphQLSchemaNormalizedConfig {
337339
return {
340+
description: this.description,
338341
query: this.getQueryType(),
339342
mutation: this.getMutationType(),
340343
subscription: this.getSubscriptionType(),
@@ -367,6 +370,7 @@ export type GraphQLSchemaValidationOptions = {|
367370
|};
368371

369372
export type GraphQLSchemaConfig = {|
373+
description?: ?string,
370374
query?: ?GraphQLObjectType,
371375
mutation?: ?GraphQLObjectType,
372376
subscription?: ?GraphQLObjectType,
@@ -383,6 +387,7 @@ export type GraphQLSchemaConfig = {|
383387
*/
384388
export type GraphQLSchemaNormalizedConfig = {|
385389
...GraphQLSchemaConfig,
390+
description: ?string,
386391
types: Array<GraphQLNamedType>,
387392
directives: Array<GraphQLDirective>,
388393
extensions: ?ReadOnlyObjMap<mixed>,

src/utilities/__tests__/buildASTSchema-test.js

+5
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ describe('Schema Builder', () => {
158158

159159
it('Supports descriptions', () => {
160160
const sdl = dedent`
161+
"""Do you agree that this is the most creative schema ever?"""
162+
schema {
163+
query: Query
164+
}
165+
161166
"""This is a directive"""
162167
directive @foo(
163168
"""It has an argument"""

src/utilities/__tests__/buildClientSchema-test.js

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function cycleIntrospection(sdlString: string): string {
5151
describe('Type System: build schema from introspection', () => {
5252
it('builds a simple schema', () => {
5353
const sdl = dedent`
54+
"""Simple schema"""
5455
schema {
5556
query: Simple
5657
}

src/utilities/__tests__/getIntrospectionQuery-test.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('getIntrospectionQuery', () => {
1818
);
1919
});
2020

21-
it('include "isRepeatable" field', () => {
21+
it('include "isRepeatable" field on directives', () => {
2222
expect(getIntrospectionQuery()).to.not.match(/\bisRepeatable\b/);
2323

2424
expect(getIntrospectionQuery({ directiveIsRepeatable: true })).to.match(
@@ -29,4 +29,26 @@ describe('getIntrospectionQuery', () => {
2929
getIntrospectionQuery({ directiveIsRepeatable: false }),
3030
).to.not.match(/\bisRepeatable\b/);
3131
});
32+
33+
it('include "description" field on schema', () => {
34+
expect(getIntrospectionQuery().match(/\bdescription\b/g)).to.have.lengthOf(
35+
5,
36+
);
37+
38+
expect(
39+
getIntrospectionQuery({ schemaDescription: false }).match(
40+
/\bdescription\b/g,
41+
),
42+
).to.have.lengthOf(5);
43+
44+
expect(
45+
getIntrospectionQuery({ schemaDescription: true }).match(
46+
/\bdescription\b/g,
47+
),
48+
).to.have.lengthOf(6);
49+
50+
expect(
51+
getIntrospectionQuery({ descriptions: false, schemaDescription: true }),
52+
).to.not.match(/\bdescription\b/);
53+
});
3254
});

src/utilities/__tests__/schemaPrinter-test.js

+21
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,23 @@ describe('Type System Printer', () => {
256256
`);
257257
});
258258

259+
it('Prints schema with description', () => {
260+
const Schema = new GraphQLSchema({
261+
description: 'Schema description.',
262+
query: new GraphQLObjectType({ name: 'Query', fields: {} }),
263+
});
264+
265+
const output = printForTest(Schema);
266+
expect(output).to.equal(dedent`
267+
"""Schema description."""
268+
schema {
269+
query: Query
270+
}
271+
272+
type Query
273+
`);
274+
});
275+
259276
it('Prints custom query root types', () => {
260277
const Schema = new GraphQLSchema({
261278
query: new GraphQLObjectType({ name: 'CustomType', fields: {} }),
@@ -626,6 +643,8 @@ describe('Type System Printer', () => {
626643
A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.
627644
"""
628645
type __Schema {
646+
description: String
647+
629648
"""A list of all types supported by this server."""
630649
types: [__Type!]!
631650
@@ -836,6 +855,8 @@ describe('Type System Printer', () => {
836855
837856
# A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.
838857
type __Schema {
858+
description: String
859+
839860
# A list of all types supported by this server.
840861
types: [__Type!]!
841862

src/utilities/buildClientSchema.js

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export function buildClientSchema(
109109

110110
// Then produce and return a Schema with these types.
111111
return new GraphQLSchema({
112+
description: schemaIntrospection.description,
112113
query: queryType,
113114
mutation: mutationType,
114115
subscription: subscriptionType,

src/utilities/extendSchema.js

+1
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export function extendSchemaImpl(
219219

220220
// Then produce and return a Schema config with these types.
221221
return {
222+
description: schemaDef?.description?.value,
222223
...operationTypes,
223224
types: objectValues(typeMap),
224225
directives: [

src/utilities/getIntrospectionQuery.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,35 @@ export type IntrospectionOptions = {|
77
// Default: true
88
descriptions?: boolean,
99

10-
// Whether to include `isRepeatable` flag on directives.
10+
// Whether to include `isRepeatable` field on directives.
1111
// Default: false
1212
directiveIsRepeatable?: boolean,
13+
14+
// Whether to include `description` field on schema.
15+
// Default: false
16+
schemaDescription?: boolean,
1317
|};
1418

1519
export function getIntrospectionQuery(options?: IntrospectionOptions): string {
1620
const optionsWithDefault = {
1721
descriptions: true,
1822
directiveIsRepeatable: false,
23+
schemaDescription: false,
1924
...options,
2025
};
2126

2227
const descriptions = optionsWithDefault.descriptions ? 'description' : '';
2328
const directiveIsRepeatable = optionsWithDefault.directiveIsRepeatable
2429
? 'isRepeatable'
2530
: '';
31+
const schemaDescription = optionsWithDefault.schemaDescription
32+
? descriptions
33+
: '';
2634

2735
return `
2836
query IntrospectionQuery {
2937
__schema {
38+
${schemaDescription}
3039
queryType { name }
3140
mutationType { name }
3241
subscriptionType { name }
@@ -125,6 +134,7 @@ export type IntrospectionQuery = {|
125134
|};
126135

127136
export type IntrospectionSchema = {|
137+
+description?: ?string,
128138
+queryType: IntrospectionNamedTypeRef<IntrospectionObjectType>,
129139
+mutationType: ?IntrospectionNamedTypeRef<IntrospectionObjectType>,
130140
+subscriptionType: ?IntrospectionNamedTypeRef<IntrospectionObjectType>,

0 commit comments

Comments
 (0)