Skip to content

Commit

Permalink
Add support for adding description to schema
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed Feb 2, 2020
1 parent d680d07 commit 80cec81
Show file tree
Hide file tree
Showing 23 changed files with 139 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/__fixtures__/schema-kitchen-sink.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""This is a description of the schema as a whole."""
schema {
query: QueryType
mutation: MutationType
Expand Down
19 changes: 19 additions & 0 deletions src/language/__tests__/schema-parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ describe('Schema Parser', () => {
);
});

it('parses schema with description string', () => {
const doc = parse(dedent`
"Description"
schema {
query: Foo
}
`);

expect(toJSONDeep(doc)).to.nested.deep.property(
'definitions[0].description',
{
kind: 'StringValue',
value: 'Description',
block: false,
loc: { start: 0, end: 13 },
},
);
});

it('Description followed by something other than type system definition throws', () => {
expectSyntaxError('"Description" 1').to.deep.equal({
message: 'Syntax Error: Unexpected Int "1".',
Expand Down
1 change: 1 addition & 0 deletions src/language/__tests__/schema-printer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('Printer: SDL document', () => {
const printed = print(parse(kitchenSinkSDL));

expect(printed).to.equal(dedent`
"""This is a description of the schema as a whole."""
schema {
query: QueryType
mutation: MutationType
Expand Down
1 change: 1 addition & 0 deletions src/language/ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ export type TypeSystemDefinitionNode =
export interface SchemaDefinitionNode {
readonly kind: 'SchemaDefinition';
readonly loc?: Location;
readonly description?: StringValueNode;
readonly directives?: ReadonlyArray<DirectiveNode>;
readonly operationTypes: ReadonlyArray<OperationTypeDefinitionNode>;
}
Expand Down
1 change: 1 addition & 0 deletions src/language/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ export type TypeSystemDefinitionNode =
export type SchemaDefinitionNode = {|
+kind: 'SchemaDefinition',
+loc?: Location,
+description?: StringValueNode,
+directives?: $ReadOnlyArray<DirectiveNode>,
+operationTypes: $ReadOnlyArray<OperationTypeDefinitionNode>,
|};
Expand Down
4 changes: 3 additions & 1 deletion src/language/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -772,10 +772,11 @@ class Parser {
}

/**
* SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ }
* SchemaDefinition : Description? schema Directives[Const]? { OperationTypeDefinition+ }
*/
parseSchemaDefinition(): SchemaDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
this.expectKeyword('schema');
const directives = this.parseDirectives(true);
const operationTypes = this.many(
Expand All @@ -785,6 +786,7 @@ class Parser {
);
return {
kind: Kind.SCHEMA_DEFINITION,
description,
directives,
operationTypes,
loc: this.loc(start),
Expand Down
3 changes: 2 additions & 1 deletion src/language/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ const printDocASTReducer: any = {

// Type System Definitions

SchemaDefinition: ({ directives, operationTypes }) =>
SchemaDefinition: addDescription(({ directives, operationTypes }) =>
join(['schema', join(directives, ' '), block(operationTypes)], ' '),
),

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

Expand Down
2 changes: 1 addition & 1 deletion src/language/visitor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const QueryDocumentKeys: {
ListType: ['type'];
NonNullType: ['type'];

SchemaDefinition: ['directives', 'operationTypes'];
SchemaDefinition: ['description', 'directives', 'operationTypes'];
OperationTypeDefinition: ['type'];

ScalarTypeDefinition: ['description', 'name', 'directives'];
Expand Down
2 changes: 1 addition & 1 deletion src/language/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const QueryDocumentKeys: VisitorKeyMap<ASTKindToNode> = {
ListType: ['type'],
NonNullType: ['type'],

SchemaDefinition: ['directives', 'operationTypes'],
SchemaDefinition: ['description', 'directives', 'operationTypes'],
OperationTypeDefinition: ['type'],

ScalarTypeDefinition: ['description', 'name', 'directives'],
Expand Down
16 changes: 16 additions & 0 deletions src/type/__tests__/introspection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
describe('Introspection', () => {
it('executes an introspection query', () => {
const schema = new GraphQLSchema({
description: 'Sample schema',
query: new GraphQLObjectType({
name: 'QueryRoot',
fields: {
Expand Down Expand Up @@ -85,6 +86,17 @@ describe('Introspection', () => {
kind: 'OBJECT',
name: '__Schema',
fields: [
{
name: 'description',
args: [],
type: {
kind: 'SCALAR',
name: 'String',
ofType: null,
},
isDeprecated: false,
deprecationReason: null,
},
{
name: 'types',
args: [],
Expand Down Expand Up @@ -1304,6 +1316,10 @@ describe('Introspection', () => {
description:
'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.',
fields: [
{
name: 'description',
description: null,
},
{
name: 'types',
description: 'A list of all types supported by this server.',
Expand Down
8 changes: 8 additions & 0 deletions src/type/__tests__/schema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,20 @@ describe('Type System: Schema', () => {
});

const schema = new GraphQLSchema({
description: 'Sample schema',
query: BlogQuery,
mutation: BlogMutation,
subscription: BlogSubscription,
});

expect(printSchema(schema)).to.equal(dedent`
"""Sample schema"""
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
type Query {
article(id: String): Article
feed: [Article]
Expand Down
4 changes: 4 additions & 0 deletions src/type/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export const __Schema = new GraphQLObjectType({
'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.',
fields: () =>
({
description: {
type: GraphQLString,
resolve: schema => schema.description,
},
types: {
description: 'A list of all types supported by this server.',
type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__Type))),
Expand Down
3 changes: 3 additions & 0 deletions src/type/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function assertSchema(schema: any): GraphQLSchema;
*
*/
export class GraphQLSchema {
description: Maybe<string>;
extensions: Maybe<Readonly<Record<string, any>>>;
astNode: Maybe<SchemaDefinitionNode>;
extensionASTNodes: Maybe<ReadonlyArray<SchemaExtensionNode>>;
Expand Down Expand Up @@ -104,6 +105,7 @@ export interface GraphQLSchemaValidationOptions {
}

export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions {
description?: Maybe<string>;
query: Maybe<GraphQLObjectType>;
mutation?: Maybe<GraphQLObjectType>;
subscription?: Maybe<GraphQLObjectType>;
Expand All @@ -118,6 +120,7 @@ export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions {
* @internal
*/
export interface GraphQLSchemaNormalizedConfig extends GraphQLSchemaConfig {
description: Maybe<string>;
types: Array<GraphQLNamedType>;
directives: Array<GraphQLDirective>;
extensions: Maybe<Readonly<Record<string, any>>>;
Expand Down
5 changes: 5 additions & 0 deletions src/type/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export function assertSchema(schema: mixed): GraphQLSchema {
*
*/
export class GraphQLSchema {
description: ?string;
extensions: ?ReadOnlyObjMap<mixed>;
astNode: ?SchemaDefinitionNode;
extensionASTNodes: ?$ReadOnlyArray<SchemaExtensionNode>;
Expand Down Expand Up @@ -157,6 +158,7 @@ export class GraphQLSchema {
`${inspect(config.directives)}.`,
);

this.description = config.description;
this.extensions = config.extensions && toObjMap(config.extensions);
this.astNode = config.astNode;
this.extensionASTNodes = config.extensionASTNodes;
Expand Down Expand Up @@ -335,6 +337,7 @@ export class GraphQLSchema {

toConfig(): GraphQLSchemaNormalizedConfig {
return {
description: this.description,
query: this.getQueryType(),
mutation: this.getMutationType(),
subscription: this.getSubscriptionType(),
Expand Down Expand Up @@ -367,6 +370,7 @@ export type GraphQLSchemaValidationOptions = {|
|};

export type GraphQLSchemaConfig = {|
description?: ?string,
query?: ?GraphQLObjectType,
mutation?: ?GraphQLObjectType,
subscription?: ?GraphQLObjectType,
Expand All @@ -383,6 +387,7 @@ export type GraphQLSchemaConfig = {|
*/
export type GraphQLSchemaNormalizedConfig = {|
...GraphQLSchemaConfig,
description: ?string,
types: Array<GraphQLNamedType>,
directives: Array<GraphQLDirective>,
extensions: ?ReadOnlyObjMap<mixed>,
Expand Down
5 changes: 5 additions & 0 deletions src/utilities/__tests__/buildASTSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ describe('Schema Builder', () => {

it('Supports descriptions', () => {
const sdl = dedent`
"""Do you agree that this is the most creative schema ever?"""
schema {
query: Query
}
"""This is a directive"""
directive @foo(
"""It has an argument"""
Expand Down
1 change: 1 addition & 0 deletions src/utilities/__tests__/buildClientSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function cycleIntrospection(sdlString: string): string {
describe('Type System: build schema from introspection', () => {
it('builds a simple schema', () => {
const sdl = dedent`
"""Simple schema"""
schema {
query: Simple
}
Expand Down
24 changes: 23 additions & 1 deletion src/utilities/__tests__/getIntrospectionQuery-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('getIntrospectionQuery', () => {
);
});

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

expect(getIntrospectionQuery({ directiveIsRepeatable: true })).to.match(
Expand All @@ -29,4 +29,26 @@ describe('getIntrospectionQuery', () => {
getIntrospectionQuery({ directiveIsRepeatable: false }),
).to.not.match(/\bisRepeatable\b/);
});

it('include "description" field on schema', () => {
expect(
getIntrospectionQuery().match(/\bdescription\b/g),
).to.have.lengthOf(5);

expect(
getIntrospectionQuery({ schemaDescription: false }).match(
/\bdescription\b/g,
),
).to.have.lengthOf(5);

expect(
getIntrospectionQuery({ schemaDescription: true }).match(
/\bdescription\b/g,
),
).to.have.lengthOf(6);

expect(
getIntrospectionQuery({ descriptions: false, schemaDescription: true }),
).to.not.match(/\bdescription\b/);
});
});
21 changes: 21 additions & 0 deletions src/utilities/__tests__/schemaPrinter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ describe('Type System Printer', () => {
`);
});

it('Prints schema with description', () => {
const Schema = new GraphQLSchema({
description: 'Schema description.',
query: new GraphQLObjectType({ name: 'Query', fields: {} }),
});

const output = printForTest(Schema);
expect(output).to.equal(dedent`
"""Schema description."""
schema {
query: Query
}
type Query
`);
});

it('Prints custom query root types', () => {
const Schema = new GraphQLSchema({
query: new GraphQLObjectType({ name: 'CustomType', fields: {} }),
Expand Down Expand Up @@ -626,6 +643,8 @@ describe('Type System Printer', () => {
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.
"""
type __Schema {
description: String
"""A list of all types supported by this server."""
types: [__Type!]!
Expand Down Expand Up @@ -836,6 +855,8 @@ describe('Type System Printer', () => {
# 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.
type __Schema {
description: String
# A list of all types supported by this server.
types: [__Type!]!
Expand Down
1 change: 1 addition & 0 deletions src/utilities/buildClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export function buildClientSchema(

// Then produce and return a Schema with these types.
return new GraphQLSchema({
description: schemaIntrospection.description,
query: queryType,
mutation: mutationType,
subscription: subscriptionType,
Expand Down
1 change: 1 addition & 0 deletions src/utilities/extendSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ export function extendSchemaImpl(

// Then produce and return a Schema config with these types.
return {
description: schemaDef?.description?.value,
...operationTypes,
types: objectValues(typeMap),
directives: [
Expand Down
12 changes: 11 additions & 1 deletion src/utilities/getIntrospectionQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,35 @@ export type IntrospectionOptions = {|
// Default: true
descriptions?: boolean,

// Whether to include `isRepeatable` flag on directives.
// Whether to include `isRepeatable` field on directives.
// Default: false
directiveIsRepeatable?: boolean,

// Whether to include `description` field on schema.
// Default: false
schemaDescription?: boolean,
|};

export function getIntrospectionQuery(options?: IntrospectionOptions): string {
const optionsWithDefault = {
descriptions: true,
directiveIsRepeatable: false,
schemaDescription: false,
...options,
};

const descriptions = optionsWithDefault.descriptions ? 'description' : '';
const directiveIsRepeatable = optionsWithDefault.directiveIsRepeatable
? 'isRepeatable'
: '';
const schemaDescription = optionsWithDefault.schemaDescription
? descriptions
: '';

return `
query IntrospectionQuery {
__schema {
${schemaDescription}
queryType { name }
mutationType { name }
subscriptionType { name }
Expand Down Expand Up @@ -125,6 +134,7 @@ export type IntrospectionQuery = {|
|};

export type IntrospectionSchema = {|
+description?: ?string,
+queryType: IntrospectionNamedTypeRef<IntrospectionObjectType>,
+mutationType: ?IntrospectionNamedTypeRef<IntrospectionObjectType>,
+subscriptionType: ?IntrospectionNamedTypeRef<IntrospectionObjectType>,
Expand Down
Loading

0 comments on commit 80cec81

Please sign in to comment.