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';
// Type System Extensions
const SCHEMA_EXTENSION = 'SchemaExtension';
/** @var string[] */
public static $classMap = [
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\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\TypeExtensionNode;
@ -1446,6 +1447,8 @@ class Parser
if ($keywordToken->kind === Token::NAME) {
switch ($keywordToken->value) {
case 'schema':
return $this->parseSchemaTypeExtension();
case 'scalar':
return $this->parseScalarTypeExtension();
case 'type':
@ -1464,6 +1467,33 @@ class Parser
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
* @throws SyntaxError

View File

@ -41,6 +41,7 @@ use GraphQL\Language\AST\OperationTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
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) {
return $this->join(
[

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
@ -24,6 +25,9 @@ class InputObjectType extends Type implements InputType, NamedType
/** @var InputObjectField[] */
private $fields;
/** @var InputObjectTypeExtensionNode[] */
public $extensionASTNodes;
/**
*
* @param mixed[] $config
@ -36,10 +40,11 @@ class InputObjectType extends Type implements InputType, NamedType
Utils::invariant(is_string($config['name']), 'Must provide name.');
$this->config = $config;
$this->name = $config['name'];
$this->astNode = $config['astNode'] ?? null;
$this->description = $config['description'] ?? null;
$this->config = $config;
$this->name = $config['name'];
$this->astNode = $config['astNode'] ?? 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;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Utils\Utils;
use function is_string;
@ -31,15 +32,19 @@ abstract class ScalarType extends Type implements OutputType, InputType, LeafTyp
/** @var ScalarTypeDefinitionNode|null */
public $astNode;
/** @var ScalarTypeExtensionNode[] */
public $extensionASTNodes;
/**
* @param mixed[] $config
*/
public function __construct(array $config = [])
{
$this->name = $config['name'] ?? $this->tryInferName();
$this->description = $config['description'] ?? $this->description;
$this->astNode = $config['astNode'] ?? null;
$this->config = $config;
$this->name = $config['name'] ?? $this->tryInferName();
$this->description = $config['description'] ?? $this->description;
$this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
$this->config = $config;
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\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
@ -27,6 +28,9 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType,
/** @var ObjectType[] */
private $possibleTypeNames;
/** @var UnionTypeExtensionNode[] */
public $extensionASTNodes;
public function __construct($config)
{
if (! isset($config['name'])) {
@ -40,10 +44,11 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType,
* the default implemenation will call `isTypeOf` on each implementing
* Object type.
*/
$this->name = $config['name'];
$this->description = $config['description'] ?? null;
$this->astNode = $config['astNode'] ?? null;
$this->config = $config;
$this->name = $config['name'];
$this->description = $config['description'] ?? null;
$this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
$this->config = $config;
}
/**

View File

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

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace GraphQL\Type;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
@ -51,6 +52,9 @@ class SchemaConfig
/** @var bool */
public $assumeValid;
/** @var SchemaTypeExtensionNode[] */
public $extensionASTNodes;
/**
* Converts an array of options to instance of SchemaConfig
* (or just returns empty config when array is not passed).
@ -100,6 +104,10 @@ class SchemaConfig
if (isset($options['assumeValid'])) {
$config->setAssumeValid((bool) $options['assumeValid']);
}
if (isset($options['extensionASTNodes'])) {
$config->setExtensionASTNodes($options['extensionASTNodes']);
}
}
return $config;
@ -266,4 +274,20 @@ class SchemaConfig
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;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\Node;
@ -12,6 +13,7 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext;
use function array_map;
use function count;
use function in_array;
use function sprintf;
@ -20,37 +22,49 @@ class KnownDirectives extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::DIRECTIVE => function (DirectiveNode $node, $key, $parent, $path, $ancestors) use ($context) {
$directiveDef = null;
foreach ($context->getSchema()->getDirectives() as $def) {
if ($def->name === $node->name->value) {
$directiveDef = $def;
break;
}
}
$locationsMap = [];
$schema = $context->getSchema();
$definedDirectives = $schema->getDirectives();
if (! $directiveDef) {
foreach ($definedDirectives as $directive) {
$locationsMap[$directive->name] = $directive->locations;
}
$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(
self::unknownDirectiveMessage($node->name->value),
self::unknownDirectiveMessage($name),
[$node]
));
return;
}
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
if (! $candidateLocation) {
$context->reportError(new Error(
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]
));
if (! $candidateLocation || in_array($candidateLocation, $locations)) {
return;
}
$context->reportError(
new Error(
self::misplacedDirectiveMessage($name, $candidateLocation),
[$node]
)
);
},
];
}
@ -88,6 +102,7 @@ class KnownDirectives extends ValidationRule
case NodeKind::FRAGMENT_DEFINITION:
return DirectiveLocation::FRAGMENT_DEFINITION;
case NodeKind::SCHEMA_DEFINITION:
case NodeKind::SCHEMA_EXTENSION:
return DirectiveLocation::SCHEMA;
case NodeKind::SCALAR_TYPE_DEFINITION:
case NodeKind::SCALAR_TYPE_EXTENSION: