Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for adding description to schema #2419

Merged
merged 1 commit into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to the isRepeatable change? Or just piggybacking in more tests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leebyron Thanks for the review 👍
Just copy-pasted tests for isRepeatable as starting point for testing that schema's description is present but forgot to change them.
I wrote a new test and committed them.

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