Merge pull request #360 from bytorsten/expand-extension

Add missing extensionASTNodes
This commit is contained in:
Vladimir Razuvaev 2018-10-02 15:08:26 +02:00 committed by GitHub
commit aed406eade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 169 additions and 40 deletions

View File

@ -69,6 +69,9 @@ class NodeKind
const DIRECTIVE_DEFINITION = 'DirectiveDefinition'; const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
// Type System Extensions
const SCHEMA_EXTENSION = 'SchemaExtension';
/** @var string[] */ /** @var string[] */
public static $classMap = [ public static $classMap = [
self::NAME => NameNode::class, self::NAME => NameNode::class,

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class SchemaTypeExtensionNode extends Node implements TypeExtensionNode
{
/** @var string */
public $kind = NodeKind::SCHEMA_EXTENSION;
/** @var DirectiveNode[]|null */
public $directives;
/** @var OperationTypeDefinitionNode[]|null */
public $operationTypes;
}

View File

@ -44,6 +44,7 @@ use GraphQL\Language\AST\OperationTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode; use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\TypeExtensionNode; use GraphQL\Language\AST\TypeExtensionNode;
@ -1446,6 +1447,8 @@ class Parser
if ($keywordToken->kind === Token::NAME) { if ($keywordToken->kind === Token::NAME) {
switch ($keywordToken->value) { switch ($keywordToken->value) {
case 'schema':
return $this->parseSchemaTypeExtension();
case 'scalar': case 'scalar':
return $this->parseScalarTypeExtension(); return $this->parseScalarTypeExtension();
case 'type': case 'type':
@ -1464,6 +1467,33 @@ class Parser
throw $this->unexpected($keywordToken); throw $this->unexpected($keywordToken);
} }
/**
* @return SchemaTypeExtensionNode
* @throws SyntaxError
*/
private function parseSchemaTypeExtension()
{
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('schema');
$directives = $this->parseDirectives(true);
$operationTypes = $this->peek(Token::BRACE_L)
? $this->many(
Token::BRACE_L,
[$this, 'parseOperationTypeDefinition'],
Token::BRACE_R
) : [];
if (count($directives) === 0 && count($operationTypes) === 0) {
$this->unexpected();
}
return new SchemaTypeExtensionNode([
'directives' => $directives,
'operationTypes' => $operationTypes,
'loc' => $this->loc($start),
]);
}
/** /**
* @return ScalarTypeExtensionNode * @return ScalarTypeExtensionNode
* @throws SyntaxError * @throws SyntaxError

View File

@ -41,6 +41,7 @@ use GraphQL\Language\AST\OperationTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode; use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode;
@ -335,6 +336,17 @@ class Printer
); );
}), }),
NodeKind::SCHEMA_EXTENSION => function (SchemaTypeExtensionNode $def) {
return $this->join(
[
'extend schema',
$this->join($def->directives, ' '),
$this->block($def->operationTypes),
],
' '
);
},
NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) { NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) {
return $this->join( return $this->join(
[ [

View File

@ -165,6 +165,8 @@ class Visitor
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'], NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'],
NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations'], NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations'],
NodeKind::SCHEMA_EXTENSION => ['directives', 'operationTypes'],
]; ];
/** /**

View File

@ -50,7 +50,7 @@ class Directive
$args = []; $args = [];
foreach ($config['args'] as $name => $arg) { foreach ($config['args'] as $name => $arg) {
if (is_array($arg)) { if (is_array($arg)) {
$args[] = FieldDefinition::create($arg + ['name' => $name]); $args[] = new FieldArgument($arg + ['name' => $name]);
} else { } else {
$args[] = $arg; $args[] = $arg;
} }

View File

@ -7,6 +7,7 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\EnumTypeDefinitionNode; use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Utils\MixedStore; use GraphQL\Utils\MixedStore;
@ -33,6 +34,9 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
/** @var \ArrayObject<string, EnumValueDefinition> */ /** @var \ArrayObject<string, EnumValueDefinition> */
private $nameLookup; private $nameLookup;
/** @var EnumTypeExtensionNode[] */
public $extensionASTNodes;
public function __construct($config) public function __construct($config)
{ {
if (! isset($config['name'])) { if (! isset($config['name'])) {
@ -44,6 +48,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
$this->name = $config['name']; $this->name = $config['name'];
$this->description = $config['description'] ?? null; $this->description = $config['description'] ?? null;
$this->astNode = $config['astNode'] ?? null; $this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
$this->config = $config; $this->config = $config;
} }

View File

@ -6,6 +6,7 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func; use function call_user_func;
use function is_array; use function is_array;
@ -24,6 +25,9 @@ class InputObjectType extends Type implements InputType, NamedType
/** @var InputObjectField[] */ /** @var InputObjectField[] */
private $fields; private $fields;
/** @var InputObjectTypeExtensionNode[] */
public $extensionASTNodes;
/** /**
* *
* @param mixed[] $config * @param mixed[] $config
@ -40,6 +44,7 @@ class InputObjectType extends Type implements InputType, NamedType
$this->name = $config['name']; $this->name = $config['name'];
$this->astNode = $config['astNode'] ?? null; $this->astNode = $config['astNode'] ?? null;
$this->description = $config['description'] ?? null; $this->description = $config['description'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
} }
/** /**

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_string; use function is_string;
@ -31,6 +32,9 @@ abstract class ScalarType extends Type implements OutputType, InputType, LeafTyp
/** @var ScalarTypeDefinitionNode|null */ /** @var ScalarTypeDefinitionNode|null */
public $astNode; public $astNode;
/** @var ScalarTypeExtensionNode[] */
public $extensionASTNodes;
/** /**
* @param mixed[] $config * @param mixed[] $config
*/ */
@ -39,6 +43,7 @@ abstract class ScalarType extends Type implements OutputType, InputType, LeafTyp
$this->name = $config['name'] ?? $this->tryInferName(); $this->name = $config['name'] ?? $this->tryInferName();
$this->description = $config['description'] ?? $this->description; $this->description = $config['description'] ?? $this->description;
$this->astNode = $config['astNode'] ?? null; $this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
$this->config = $config; $this->config = $config;
Utils::invariant(is_string($this->name), 'Must provide name.'); Utils::invariant(is_string($this->name), 'Must provide name.');

View File

@ -6,6 +6,7 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func; use function call_user_func;
use function is_array; use function is_array;
@ -27,6 +28,9 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType,
/** @var ObjectType[] */ /** @var ObjectType[] */
private $possibleTypeNames; private $possibleTypeNames;
/** @var UnionTypeExtensionNode[] */
public $extensionASTNodes;
public function __construct($config) public function __construct($config)
{ {
if (! isset($config['name'])) { if (! isset($config['name'])) {
@ -43,6 +47,7 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType,
$this->name = $config['name']; $this->name = $config['name'];
$this->description = $config['description'] ?? null; $this->description = $config['description'] ?? null;
$this->astNode = $config['astNode'] ?? null; $this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
$this->config = $config; $this->config = $config;
} }

View File

@ -8,6 +8,7 @@ use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
@ -67,6 +68,9 @@ class Schema
/** @var InvariantViolation[]|null */ /** @var InvariantViolation[]|null */
private $validationErrors; private $validationErrors;
/** @var SchemaTypeExtensionNode[] */
public $extensionASTNodes;
/** /**
* @api * @api
* @param mixed[]|SchemaConfig $config * @param mixed[]|SchemaConfig $config
@ -111,6 +115,8 @@ class Schema
} }
$this->config = $config; $this->config = $config;
$this->extensionASTNodes = $config->extensionASTNodes;
if ($config->query) { if ($config->query) {
$this->resolvedTypes[$config->query->name] = $config->query; $this->resolvedTypes[$config->query->name] = $config->query;
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
@ -51,6 +52,9 @@ class SchemaConfig
/** @var bool */ /** @var bool */
public $assumeValid; public $assumeValid;
/** @var SchemaTypeExtensionNode[] */
public $extensionASTNodes;
/** /**
* Converts an array of options to instance of SchemaConfig * Converts an array of options to instance of SchemaConfig
* (or just returns empty config when array is not passed). * (or just returns empty config when array is not passed).
@ -100,6 +104,10 @@ class SchemaConfig
if (isset($options['assumeValid'])) { if (isset($options['assumeValid'])) {
$config->setAssumeValid((bool) $options['assumeValid']); $config->setAssumeValid((bool) $options['assumeValid']);
} }
if (isset($options['extensionASTNodes'])) {
$config->setExtensionASTNodes($options['extensionASTNodes']);
}
} }
return $config; return $config;
@ -266,4 +274,20 @@ class SchemaConfig
return $this; return $this;
} }
/**
* @return SchemaTypeExtensionNode[]
*/
public function getExtensionASTNodes()
{
return $this->extensionASTNodes;
}
/**
* @param SchemaTypeExtensionNode[] $extensionASTNodes
*/
public function setExtensionASTNodes(array $extensionASTNodes)
{
$this->extensionASTNodes = $extensionASTNodes;
}
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules; namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
@ -12,6 +13,7 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NodeList;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use function array_map;
use function count; use function count;
use function in_array; use function in_array;
use function sprintf; use function sprintf;
@ -20,37 +22,49 @@ class KnownDirectives extends ValidationRule
{ {
public function getVisitor(ValidationContext $context) public function getVisitor(ValidationContext $context)
{ {
return [ $locationsMap = [];
NodeKind::DIRECTIVE => function (DirectiveNode $node, $key, $parent, $path, $ancestors) use ($context) { $schema = $context->getSchema();
$directiveDef = null; $definedDirectives = $schema->getDirectives();
foreach ($context->getSchema()->getDirectives() as $def) {
if ($def->name === $node->name->value) { foreach ($definedDirectives as $directive) {
$directiveDef = $def; $locationsMap[$directive->name] = $directive->locations;
break;
}
} }
if (! $directiveDef) { $astDefinition = $context->getDocument()->definitions;
foreach ($astDefinition as $def) {
if (! ($def instanceof DirectiveDefinitionNode)) {
continue;
}
$locationsMap[$def->name->value] = array_map(function ($name) {
return $name->value;
}, $def->locations);
}
return [
NodeKind::DIRECTIVE => function (DirectiveNode $node, $key, $parent, $path, $ancestors) use ($context, $locationsMap) {
$name = $node->name->value;
$locations = $locationsMap[$name] ?? null;
if (! $locations) {
$context->reportError(new Error( $context->reportError(new Error(
self::unknownDirectiveMessage($node->name->value), self::unknownDirectiveMessage($name),
[$node] [$node]
)); ));
return; return;
} }
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors); $candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
if (! $candidateLocation) { if (! $candidateLocation || in_array($candidateLocation, $locations)) {
$context->reportError(new Error( return;
self::misplacedDirectiveMessage($node->name->value, $node->type),
[$node]
));
} elseif (! in_array($candidateLocation, $directiveDef->locations)) {
$context->reportError(new Error(
self::misplacedDirectiveMessage($node->name->value, $candidateLocation),
[$node]
));
} }
$context->reportError(
new Error(
self::misplacedDirectiveMessage($name, $candidateLocation),
[$node]
)
);
}, },
]; ];
} }
@ -88,6 +102,7 @@ class KnownDirectives extends ValidationRule
case NodeKind::FRAGMENT_DEFINITION: case NodeKind::FRAGMENT_DEFINITION:
return DirectiveLocation::FRAGMENT_DEFINITION; return DirectiveLocation::FRAGMENT_DEFINITION;
case NodeKind::SCHEMA_DEFINITION: case NodeKind::SCHEMA_DEFINITION:
case NodeKind::SCHEMA_EXTENSION:
return DirectiveLocation::SCHEMA; return DirectiveLocation::SCHEMA;
case NodeKind::SCALAR_TYPE_DEFINITION: case NodeKind::SCALAR_TYPE_DEFINITION:
case NodeKind::SCALAR_TYPE_EXTENSION: case NodeKind::SCALAR_TYPE_EXTENSION: