Skip to content

Commit

Permalink
RFC: SchemaExtension
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
leebyron committed Apr 19, 2018
1 parent 3cb4500 commit 7a93bba
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 19 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
}
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
}
`);
});
});
37 changes: 25 additions & 12 deletions src/language/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export type ASTNode =
| EnumTypeDefinitionNode
| EnumValueDefinitionNode
| InputObjectTypeDefinitionNode
| SchemaExtensionNode
| ScalarTypeExtensionNode
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
Expand Down Expand Up @@ -171,6 +172,7 @@ export type ASTKindToNode = {
EnumTypeDefinition: EnumTypeDefinitionNode,
EnumValueDefinition: EnumValueDefinitionNode,
InputObjectTypeDefinition: InputObjectTypeDefinitionNode,
SchemaExtension: SchemaExtensionNode,
ScalarTypeExtension: ScalarTypeExtensionNode,
ObjectTypeExtension: ObjectTypeExtensionNode,
InterfaceTypeExtension: InterfaceTypeExtensionNode,
Expand Down Expand Up @@ -388,7 +390,7 @@ export type NonNullTypeNode = {
export type TypeSystemDefinitionNode =
| SchemaDefinitionNode
| TypeDefinitionNode
| TypeExtensionNode
| TypeSystemExtensionNode
| DirectiveDefinitionNode;

export type SchemaDefinitionNode = {
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
3 changes: 3 additions & 0 deletions src/language/kinds.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export const Kind = Object.freeze({
ENUM_VALUE_DEFINITION: 'EnumValueDefinition',
INPUT_OBJECT_TYPE_DEFINITION: 'InputObjectTypeDefinition',

// Type System Extensions
SCHEMA_EXTENSION: 'SchemaExtension',

// Type Extensions
SCALAR_TYPE_EXTENSION: 'ScalarTypeExtension',
OBJECT_TYPE_EXTENSION: 'ObjectTypeExtension',
Expand Down
44 changes: 40 additions & 4 deletions src/language/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import type {
EnumTypeDefinitionNode,
EnumValueDefinitionNode,
InputObjectTypeDefinitionNode,
TypeExtensionNode,
TypeSystemExtensionNode,
SchemaExtensionNode,
ScalarTypeExtensionNode,
ObjectTypeExtensionNode,
InterfaceTypeExtensionNode,
Expand Down Expand Up @@ -751,9 +752,9 @@ export function parseNamedType(lexer: Lexer<*>): NamedTypeNode {

/**
* TypeSystemDefinition :
* - TypeSystemExtension
* - SchemaDefinition
* - TypeDefinition
* - TypeExtension
* - DirectiveDefinition
*
* TypeDefinition :
Expand Down Expand Up @@ -785,7 +786,7 @@ function parseTypeSystemDefinition(lexer: Lexer<*>): TypeSystemDefinitionNode {
case 'input':
return parseInputObjectTypeDefinition(lexer);
case 'extend':
return parseTypeExtension(lexer);
return parseTypeSystemExtension(lexer);
case 'directive':
return parseDirectiveDefinition(lexer);
}
Expand Down Expand Up @@ -1141,6 +1142,10 @@ function parseInputFieldsDefinition(
}

/**
* TypeSystemExtension :
* - SchemaExtension
* - TypeExtension
*
* TypeExtension :
* - ScalarTypeExtension
* - ObjectTypeExtension
Expand All @@ -1149,11 +1154,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 +1179,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
3 changes: 3 additions & 0 deletions src/language/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ const printDocASTReducer = {
join(['input', name, join(directives, ' '), block(fields)], ' '),
),

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

ScalarTypeExtension: ({ name, directives }) =>
join(['extend scalar', name, join(directives, ' ')], ' '),

Expand Down
1 change: 1 addition & 0 deletions src/language/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const QueryDocumentKeys = {
NonNullType: ['type'],

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

ScalarTypeDefinition: ['description', 'name', 'directives'],
Expand Down
3 changes: 2 additions & 1 deletion src/validation/rules/ExecutableDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export function ExecutableDefinitions(context: ValidationContext): ASTVisitor {
context.reportError(
new GraphQLError(
nonExecutableDefinitionMessage(
definition.kind === Kind.SCHEMA_DEFINITION
definition.kind === Kind.SCHEMA_DEFINITION ||
definition.kind === Kind.SCHEMA_EXTENSION
? 'schema'
: definition.name.value,
),
Expand Down
1 change: 1 addition & 0 deletions src/validation/rules/KnownDirectives.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function getDirectiveLocationForASTPath(ancestors) {
case Kind.FRAGMENT_DEFINITION:
return DirectiveLocation.FRAGMENT_DEFINITION;
case Kind.SCHEMA_DEFINITION:
case Kind.SCHEMA_EXTENSION:
return DirectiveLocation.SCHEMA;
case Kind.SCALAR_TYPE_DEFINITION:
case Kind.SCALAR_TYPE_EXTENSION:
Expand Down

0 comments on commit 7a93bba

Please sign in to comment.