Skip to content

Commit

Permalink
RFC: SchemaExtension (#1314)
Browse files Browse the repository at this point in the history
* RFC: SchemaExtension

This adds support for graphql/graphql-spec#428 spec proposal.

So far this just adds language support and updates validation rules to be aware of this new ast node. I'll follow up with support in `extendSchema()` and tests.

* Support extendSchema()

* Formatting edits

* Add parsing and validation tests

* Adjust grammar rules to match spec definitions
  • Loading branch information
leebyron committed Apr 23, 2018
1 parent 3cb4500 commit 5dc773e
Show file tree
Hide file tree
Showing 17 changed files with 416 additions and 63 deletions.
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,16 @@ export type {
EnumTypeDefinitionNode,
EnumValueDefinitionNode,
InputObjectTypeDefinitionNode,
DirectiveDefinitionNode,
TypeSystemExtensionNode,
SchemaExtensionNode,
TypeExtensionNode,
ScalarTypeExtensionNode,
ObjectTypeExtensionNode,
InterfaceTypeExtensionNode,
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
DirectiveDefinitionNode,
KindEnum,
TokenKindEnum,
DirectiveLocationEnum,
Expand Down
6 changes: 6 additions & 0 deletions src/language/__tests__/schema-kitchen-sink.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,9 @@ directive @include2(if: Boolean!) on
| FIELD
| FRAGMENT_SPREAD
| INLINE_FRAGMENT

extend schema @onSchema

extend schema @onSchema {
subscription: SubscriptionType
}
60 changes: 60 additions & 0 deletions src/language/__tests__/schema-parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,66 @@ extend type Hello {
);
});

it('Schema extension', () => {
const body = `
extend schema {
mutation: Mutation
}`;
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'SchemaExtension',
directives: [],
operationTypes: [
{
kind: 'OperationTypeDefinition',
operation: 'mutation',
type: typeNode('Mutation', { start: 41, end: 49 }),
loc: { start: 31, end: 49 },
},
],
loc: { start: 7, end: 57 },
},
],
loc: { start: 0, end: 57 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Schema extension with only directives', () => {
const body = 'extend schema @directive';
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'SchemaExtension',
directives: [
{
kind: 'Directive',
name: nameNode('directive', { start: 15, end: 24 }),
arguments: [],
loc: { start: 14, end: 24 },
},
],
operationTypes: [],
loc: { start: 0, end: 24 },
},
],
loc: { start: 0, end: 24 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Schema extension without anything throws', () => {
expectSyntaxError('extend schema', 'Unexpected <EOF>', {
line: 1,
column: 14,
});
});

it('Simple non-null type', () => {
const body = `
type Hello {
Expand Down
6 changes: 6 additions & 0 deletions src/language/__tests__/schema-printer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ describe('Printer: SDL document', () => {
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
extend schema @onSchema
extend schema @onSchema {
subscription: SubscriptionType
}
`);
});
});
47 changes: 30 additions & 17 deletions src/language/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,14 @@ export type ASTNode =
| EnumTypeDefinitionNode
| EnumValueDefinitionNode
| InputObjectTypeDefinitionNode
| DirectiveDefinitionNode
| SchemaExtensionNode
| ScalarTypeExtensionNode
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode
| DirectiveDefinitionNode;
| InputObjectTypeExtensionNode;

/**
* Utility type listing all nodes indexed by their kind.
Expand Down Expand Up @@ -171,13 +172,14 @@ export type ASTKindToNode = {
EnumTypeDefinition: EnumTypeDefinitionNode,
EnumValueDefinition: EnumValueDefinitionNode,
InputObjectTypeDefinition: InputObjectTypeDefinitionNode,
DirectiveDefinition: DirectiveDefinitionNode,
SchemaExtension: SchemaExtensionNode,
ScalarTypeExtension: ScalarTypeExtensionNode,
ObjectTypeExtension: ObjectTypeExtensionNode,
InterfaceTypeExtension: InterfaceTypeExtensionNode,
UnionTypeExtension: UnionTypeExtensionNode,
EnumTypeExtension: EnumTypeExtensionNode,
InputObjectTypeExtension: InputObjectTypeExtensionNode,
DirectiveDefinition: DirectiveDefinitionNode,
};

// Name
Expand All @@ -198,7 +200,8 @@ export type DocumentNode = {

export type DefinitionNode =
| ExecutableDefinitionNode
| TypeSystemDefinitionNode; // experimental non-spec addition.
| TypeSystemDefinitionNode
| TypeSystemExtensionNode;

export type ExecutableDefinitionNode =
| OperationDefinitionNode
Expand Down Expand Up @@ -388,13 +391,12 @@ export type NonNullTypeNode = {
export type TypeSystemDefinitionNode =
| SchemaDefinitionNode
| TypeDefinitionNode
| TypeExtensionNode
| DirectiveDefinitionNode;

export type SchemaDefinitionNode = {
+kind: 'SchemaDefinition',
+loc?: Location,
+directives: $ReadOnlyArray<DirectiveNode>,
+directives?: $ReadOnlyArray<DirectiveNode>,
+operationTypes: $ReadOnlyArray<OperationTypeDefinitionNode>,
};

Expand Down Expand Up @@ -497,6 +499,28 @@ export type InputObjectTypeDefinitionNode = {
+fields?: $ReadOnlyArray<InputValueDefinitionNode>,
};

// Directive Definitions

export type DirectiveDefinitionNode = {
+kind: 'DirectiveDefinition',
+loc?: Location,
+description?: StringValueNode,
+name: NameNode,
+arguments?: $ReadOnlyArray<InputValueDefinitionNode>,
+locations: $ReadOnlyArray<NameNode>,
};

// Type System Extensions

export type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode;

export type SchemaExtensionNode = {
+kind: 'SchemaExtension',
+loc?: Location,
+directives?: $ReadOnlyArray<DirectiveNode>,
+operationTypes?: $ReadOnlyArray<OperationTypeDefinitionNode>,
};

// Type Extensions

export type TypeExtensionNode =
Expand Down Expand Up @@ -554,14 +578,3 @@ export type InputObjectTypeExtensionNode = {
+directives?: $ReadOnlyArray<DirectiveNode>,
+fields?: $ReadOnlyArray<InputValueDefinitionNode>,
};

// Directive Definitions

export type DirectiveDefinitionNode = {
+kind: 'DirectiveDefinition',
+loc?: Location,
+description?: StringValueNode,
+name: NameNode,
+arguments?: $ReadOnlyArray<InputValueDefinitionNode>,
+locations: $ReadOnlyArray<NameNode>,
};
4 changes: 3 additions & 1 deletion src/language/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,16 @@ export type {
EnumTypeDefinitionNode,
EnumValueDefinitionNode,
InputObjectTypeDefinitionNode,
DirectiveDefinitionNode,
TypeSystemExtensionNode,
SchemaExtensionNode,
TypeExtensionNode,
ScalarTypeExtensionNode,
ObjectTypeExtensionNode,
InterfaceTypeExtensionNode,
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
DirectiveDefinitionNode,
} from './ast';

export { DirectiveLocation } from './directiveLocation';
Expand Down
9 changes: 6 additions & 3 deletions src/language/kinds.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,19 @@ export const Kind = Object.freeze({
ENUM_VALUE_DEFINITION: 'EnumValueDefinition',
INPUT_OBJECT_TYPE_DEFINITION: 'InputObjectTypeDefinition',

// Directive Definitions
DIRECTIVE_DEFINITION: 'DirectiveDefinition',

// Type System Extensions
SCHEMA_EXTENSION: 'SchemaExtension',

// Type Extensions
SCALAR_TYPE_EXTENSION: 'ScalarTypeExtension',
OBJECT_TYPE_EXTENSION: 'ObjectTypeExtension',
INTERFACE_TYPE_EXTENSION: 'InterfaceTypeExtension',
UNION_TYPE_EXTENSION: 'UnionTypeExtension',
ENUM_TYPE_EXTENSION: 'EnumTypeExtension',
INPUT_OBJECT_TYPE_EXTENSION: 'InputObjectTypeExtension',

// Directive Definitions
DIRECTIVE_DEFINITION: 'DirectiveDefinition',
});

/**
Expand Down
51 changes: 42 additions & 9 deletions src/language/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ import type {
EnumTypeDefinitionNode,
EnumValueDefinitionNode,
InputObjectTypeDefinitionNode,
TypeExtensionNode,
DirectiveDefinitionNode,
TypeSystemExtensionNode,
SchemaExtensionNode,
ScalarTypeExtensionNode,
ObjectTypeExtensionNode,
InterfaceTypeExtensionNode,
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
DirectiveDefinitionNode,
} from './ast';

import { Kind } from './kinds';
Expand Down Expand Up @@ -211,6 +212,7 @@ function parseDocument(lexer: Lexer<*>): DocumentNode {
* Definition :
* - ExecutableDefinition
* - TypeSystemDefinition
* - TypeSystemExtension
*/
function parseDefinition(lexer: Lexer<*>): DefinitionNode {
if (peek(lexer, TokenKind.NAME)) {
Expand All @@ -227,15 +229,14 @@ function parseDefinition(lexer: Lexer<*>): DefinitionNode {
case 'union':
case 'enum':
case 'input':
case 'extend':
case 'directive':
// Note: The schema definition language is an experimental addition.
return parseTypeSystemDefinition(lexer);
case 'extend':
return parseTypeSystemExtension(lexer);
}
} else if (peek(lexer, TokenKind.BRACE_L)) {
return parseExecutableDefinition(lexer);
} else if (peekDescription(lexer)) {
// Note: The schema definition language is an experimental addition.
return parseTypeSystemDefinition(lexer);
}

Expand Down Expand Up @@ -753,7 +754,6 @@ export function parseNamedType(lexer: Lexer<*>): NamedTypeNode {
* TypeSystemDefinition :
* - SchemaDefinition
* - TypeDefinition
* - TypeExtension
* - DirectiveDefinition
*
* TypeDefinition :
Expand Down Expand Up @@ -784,8 +784,6 @@ function parseTypeSystemDefinition(lexer: Lexer<*>): TypeSystemDefinitionNode {
return parseEnumTypeDefinition(lexer);
case 'input':
return parseInputObjectTypeDefinition(lexer);
case 'extend':
return parseTypeExtension(lexer);
case 'directive':
return parseDirectiveDefinition(lexer);
}
Expand Down Expand Up @@ -1141,6 +1139,10 @@ function parseInputFieldsDefinition(
}

/**
* TypeSystemExtension :
* - SchemaExtension
* - TypeExtension
*
* TypeExtension :
* - ScalarTypeExtension
* - ObjectTypeExtension
Expand All @@ -1149,11 +1151,13 @@ function parseInputFieldsDefinition(
* - EnumTypeExtension
* - InputObjectTypeDefinition
*/
function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode {
function parseTypeSystemExtension(lexer: Lexer<*>): TypeSystemExtensionNode {
const keywordToken = lexer.lookahead();

if (keywordToken.kind === TokenKind.NAME) {
switch (keywordToken.value) {
case 'schema':
return parseSchemaExtension(lexer);
case 'scalar':
return parseScalarTypeExtension(lexer);
case 'type':
Expand All @@ -1172,6 +1176,35 @@ function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode {
throw unexpected(lexer, keywordToken);
}

/**
* SchemaExtension :
* - extend schema Directives[Const]? { OperationTypeDefinition+ }
* - extend schema Directives[Const]
*/
function parseSchemaExtension(lexer: Lexer<*>): SchemaExtensionNode {
const start = lexer.token;
expectKeyword(lexer, 'extend');
expectKeyword(lexer, 'schema');
const directives = parseDirectives(lexer, true);
const operationTypes = peek(lexer, TokenKind.BRACE_L)
? many(
lexer,
TokenKind.BRACE_L,
parseOperationTypeDefinition,
TokenKind.BRACE_R,
)
: [];
if (directives.length === 0 && operationTypes.length === 0) {
throw unexpected(lexer);
}
return {
kind: Kind.SCHEMA_EXTENSION,
directives,
operationTypes,
loc: loc(lexer, start),
};
}

/**
* ScalarTypeExtension :
* - extend scalar Name Directives[Const]
Expand Down
Loading

0 comments on commit 5dc773e

Please sign in to comment.