Update to match SDL changes

This changes the parsing grammar and validation rules to more correctly implement the current state of the GraphQL SDL proposal (facebook/graphql#90)

ref: graphql/graphl-js#1102
This commit is contained in:
Daniel Tschinder 2018-02-11 13:27:26 +01:00
parent 0c32982171
commit d70a9a5e53
29 changed files with 522 additions and 194 deletions

View File

@ -374,28 +374,28 @@ public $variableValues;
*/
function getFieldSelection($depth = 0)
```
# GraphQL\Type\Definition\DirectiveLocation
# GraphQL\Language\DirectiveLocation
List of available directive locations
**Class Constants:**
```php
const IFACE = "INTERFACE";
const SUBSCRIPTION = "SUBSCRIPTION";
const FRAGMENT_SPREAD = "FRAGMENT_SPREAD";
const QUERY = "QUERY";
const MUTATION = "MUTATION";
const SUBSCRIPTION = "SUBSCRIPTION";
const FIELD = "FIELD";
const FRAGMENT_DEFINITION = "FRAGMENT_DEFINITION";
const INPUT_OBJECT = "INPUT_OBJECT";
const FRAGMENT_SPREAD = "FRAGMENT_SPREAD";
const INLINE_FRAGMENT = "INLINE_FRAGMENT";
const UNION = "UNION";
const SCHEMA = "SCHEMA";
const SCALAR = "SCALAR";
const OBJECT = "OBJECT";
const FIELD_DEFINITION = "FIELD_DEFINITION";
const ARGUMENT_DEFINITION = "ARGUMENT_DEFINITION";
const IFACE = "INTERFACE";
const UNION = "UNION";
const ENUM = "ENUM";
const OBJECT = "OBJECT";
const ENUM_VALUE = "ENUM_VALUE";
const FIELD = "FIELD";
const SCHEMA = "SCHEMA";
const INPUT_OBJECT = "INPUT_OBJECT";
const INPUT_FIELD_DEFINITION = "INPUT_FIELD_DEFINITION";
```
@ -936,7 +936,7 @@ const UNION_TYPE_DEFINITION = "UnionTypeDefinition";
const ENUM_TYPE_DEFINITION = "EnumTypeDefinition";
const ENUM_VALUE_DEFINITION = "EnumValueDefinition";
const INPUT_OBJECT_TYPE_DEFINITION = "InputObjectTypeDefinition";
const TYPE_EXTENSION_DEFINITION = "TypeExtensionDefinition";
const OBJECT_TYPE_EXTENSION = "ObjectTypeExtension";
const DIRECTIVE_DEFINITION = "DirectiveDefinition";
```

View File

@ -35,9 +35,9 @@ In **graphql-php** custom directive is an instance of `GraphQL\Type\Definition\D
```php
<?php
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\DirectiveLocation;
use GraphQL\Type\Definition\FieldArgument;
$trackDirective = new Directive([

View File

@ -211,11 +211,6 @@ class Executor
case NodeKind::FRAGMENT_DEFINITION:
$fragments[$definition->name->value] = $definition;
break;
default:
throw new Error(
"GraphQL cannot execute a request containing a {$definition->kind}.",
[$definition]
);
}
}

View File

@ -65,7 +65,7 @@ class NodeKind
// Type Extensions
const TYPE_EXTENSION_DEFINITION = 'TypeExtensionDefinition';
const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
// Directive Definitions
@ -127,7 +127,7 @@ class NodeKind
NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
// Type Extensions
NodeKind::TYPE_EXTENSION_DEFINITION => TypeExtensionDefinitionNode::class,
NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
// Directive Definitions
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class

View File

@ -0,0 +1,30 @@
<?php
namespace GraphQL\Language\AST;
class ObjectTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
public $kind = NodeKind::OBJECT_TYPE_EXTENSION;
/**
* @var NameNode
*/
public $name;
/**
* @var NamedTypeNode[]
*/
public $interfaces = [];
/**
* @var DirectiveNode[]
*/
public $directives;
/**
* @var FieldDefinitionNode[]
*/
public $fields;
}

View File

@ -1,15 +0,0 @@
<?php
namespace GraphQL\Language\AST;
class TypeExtensionDefinitionNode extends Node implements TypeSystemDefinitionNode
{
/**
* @var string
*/
public $kind = NodeKind::TYPE_EXTENSION_DEFINITION;
/**
* @var ObjectTypeDefinitionNode
*/
public $definition;
}

View File

@ -0,0 +1,10 @@
<?php
namespace GraphQL\Language\AST;
interface TypeExtensionNode extends TypeSystemDefinitionNode
{
/**
export type TypeExtensionNode =
| ObjectTypeExtensionNode;
*/
}

View File

@ -4,9 +4,10 @@ namespace GraphQL\Language\AST;
interface TypeSystemDefinitionNode extends DefinitionNode
{
/**
export type TypeSystemDefinitionNode = SchemaDefinitionNode
export type TypeSystemDefinitionNode =
| SchemaDefinitionNode
| TypeDefinitionNode
| TypeExtensionDefinitionNode
| TypeExtensionNode
| DirectiveDefinitionNode
*/
}

View File

@ -0,0 +1,60 @@
<?php
namespace GraphQL\Language;
/**
* List of available directive locations
*/
class DirectiveLocation
{
// Request Definitions
const QUERY = 'QUERY';
const MUTATION = 'MUTATION';
const SUBSCRIPTION = 'SUBSCRIPTION';
const FIELD = 'FIELD';
const FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION';
const FRAGMENT_SPREAD = 'FRAGMENT_SPREAD';
const INLINE_FRAGMENT = 'INLINE_FRAGMENT';
// Type System Definitions
const SCHEMA = 'SCHEMA';
const SCALAR = 'SCALAR';
const OBJECT = 'OBJECT';
const FIELD_DEFINITION = 'FIELD_DEFINITION';
const ARGUMENT_DEFINITION = 'ARGUMENT_DEFINITION';
const IFACE = 'INTERFACE';
const UNION = 'UNION';
const ENUM = 'ENUM';
const ENUM_VALUE = 'ENUM_VALUE';
const INPUT_OBJECT = 'INPUT_OBJECT';
const INPUT_FIELD_DEFINITION = 'INPUT_FIELD_DEFINITION';
private static $locations = [
self::QUERY => self::QUERY,
self::MUTATION => self::MUTATION,
self::SUBSCRIPTION => self::SUBSCRIPTION,
self::FIELD => self::FIELD,
self::FRAGMENT_DEFINITION => self::FRAGMENT_DEFINITION,
self::FRAGMENT_SPREAD => self::FRAGMENT_SPREAD,
self::INLINE_FRAGMENT => self::INLINE_FRAGMENT,
self::SCHEMA => self::SCHEMA,
self::SCALAR => self::SCALAR,
self::OBJECT => self::OBJECT,
self::FIELD_DEFINITION => self::FIELD_DEFINITION,
self::ARGUMENT_DEFINITION => self::ARGUMENT_DEFINITION,
self::IFACE => self::IFACE,
self::UNION => self::UNION,
self::ENUM => self::ENUM,
self::ENUM_VALUE => self::ENUM_VALUE,
self::INPUT_OBJECT => self::INPUT_OBJECT,
self::INPUT_FIELD_DEFINITION => self::INPUT_FIELD_DEFINITION,
];
/**
* @param string $name
* @return bool
*/
public static function has($name)
{
return isset(self::$locations[$name]);
}
}

View File

@ -36,7 +36,8 @@ use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\TypeExtensionNode;
use GraphQL\Language\AST\TypeSystemDefinitionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\VariableNode;
@ -393,7 +394,7 @@ class Parser
'operation' => $operation,
'name' => $name,
'variableDefinitions' => $this->parseVariableDefinitions(),
'directives' => $this->parseDirectives(),
'directives' => $this->parseDirectives(false),
'selectionSet' => $this->parseSelectionSet(),
'loc' => $this->loc($start)
]);
@ -494,6 +495,7 @@ class Parser
/**
* @return FieldNode
* @throws SyntaxError
*/
function parseField()
{
@ -511,20 +513,23 @@ class Parser
return new FieldNode([
'alias' => $alias,
'name' => $name,
'arguments' => $this->parseArguments(),
'directives' => $this->parseDirectives(),
'arguments' => $this->parseArguments(false),
'directives' => $this->parseDirectives(false),
'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null,
'loc' => $this->loc($start)
]);
}
/**
* @param bool $isConst
* @return ArgumentNode[]|NodeList
* @throws SyntaxError
*/
function parseArguments()
function parseArguments($isConst)
{
$item = $isConst ? 'parseConstArgument' : 'parseArgument';
return $this->peek(Token::PAREN_L) ?
$this->many(Token::PAREN_L, [$this, 'parseArgument'], Token::PAREN_R) :
$this->many(Token::PAREN_L, [$this, $item], Token::PAREN_R) :
new NodeList([]);
}
@ -547,6 +552,25 @@ class Parser
]);
}
/**
* @return ArgumentNode
* @throws SyntaxError
*/
function parseConstArgument()
{
$start = $this->lexer->token;
$name = $this->parseName();
$this->expect(Token::COLON);
$value = $this->parseConstValue();
return new ArgumentNode([
'name' => $name,
'value' => $value,
'loc' => $this->loc($start)
]);
}
// Implements the parsing rules in the Fragments section.
/**
@ -561,7 +585,7 @@ class Parser
if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') {
return new FragmentSpreadNode([
'name' => $this->parseFragmentName(),
'directives' => $this->parseDirectives(),
'directives' => $this->parseDirectives(false),
'loc' => $this->loc($start)
]);
}
@ -574,7 +598,7 @@ class Parser
return new InlineFragmentNode([
'typeCondition' => $typeCondition,
'directives' => $this->parseDirectives(),
'directives' => $this->parseDirectives(false),
'selectionSet' => $this->parseSelectionSet(),
'loc' => $this->loc($start)
]);
@ -596,7 +620,7 @@ class Parser
return new FragmentDefinitionNode([
'name' => $name,
'typeCondition' => $typeCondition,
'directives' => $this->parseDirectives(),
'directives' => $this->parseDirectives(false),
'selectionSet' => $this->parseSelectionSet(),
'loc' => $this->loc($start)
]);
@ -775,28 +799,31 @@ class Parser
// Implements the parsing rules in the Directives section.
/**
* @param bool $isConst
* @return DirectiveNode[]|NodeList
* @throws SyntaxError
*/
function parseDirectives()
function parseDirectives($isConst)
{
$directives = [];
while ($this->peek(Token::AT)) {
$directives[] = $this->parseDirective();
$directives[] = $this->parseDirective($isConst);
}
return new NodeList($directives);
}
/**
* @param bool $isConst
* @return DirectiveNode
* @throws SyntaxError
*/
function parseDirective()
function parseDirective($isConst)
{
$start = $this->lexer->token;
$this->expect(Token::AT);
return new DirectiveNode([
'name' => $this->parseName(),
'arguments' => $this->parseArguments(),
'arguments' => $this->parseArguments($isConst),
'loc' => $this->loc($start)
]);
}
@ -849,7 +876,7 @@ class Parser
* TypeSystemDefinition :
* - SchemaDefinition
* - TypeDefinition
* - TypeExtensionDefinition
* - TypeExtension
* - DirectiveDefinition
*
* TypeDefinition :
@ -879,12 +906,12 @@ class Parser
case 'union': return $this->parseUnionTypeDefinition();
case 'enum': return $this->parseEnumTypeDefinition();
case 'input': return $this->parseInputObjectTypeDefinition();
case 'extend': return $this->parseTypeExtensionDefinition();
case 'extend': return $this->parseTypeExtension();
case 'directive': return $this->parseDirectiveDefinition();
}
}
throw $this->unexpected();
throw $this->unexpected($keywordToken);
}
/**
@ -911,7 +938,7 @@ class Parser
{
$start = $this->lexer->token;
$this->expectKeyword('schema');
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
$operationTypes = $this->many(
Token::BRACE_L,
@ -953,7 +980,7 @@ class Parser
$description = $this->parseDescription();
$this->expectKeyword('scalar');
$name = $this->parseName();
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
return new ScalarTypeDefinitionNode([
'name' => $name,
@ -974,13 +1001,8 @@ class Parser
$this->expectKeyword('type');
$name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives();
$fields = $this->any(
Token::BRACE_L,
[$this, 'parseFieldDefinition'],
Token::BRACE_R
);
$directives = $this->parseDirectives(true);
$fields = $this->parseFieldDefinitions();
return new ObjectTypeDefinitionNode([
'name' => $name,
@ -1007,6 +1029,19 @@ class Parser
return $types;
}
/**
* @return FieldDefinitionNode[]|NodeList
* @throws SyntaxError
*/
function parseFieldDefinitions()
{
return $this->many(
Token::BRACE_L,
[$this, 'parseFieldDefinition'],
Token::BRACE_R
);
}
/**
* @return FieldDefinitionNode
* @throws SyntaxError
@ -1019,7 +1054,7 @@ class Parser
$args = $this->parseArgumentDefs();
$this->expect(Token::COLON);
$type = $this->parseTypeReference();
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
return new FieldDefinitionNode([
'name' => $name,
@ -1057,7 +1092,7 @@ class Parser
if ($this->skip(Token::EQUALS)) {
$defaultValue = $this->parseConstValue();
}
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
return new InputValueDefinitionNode([
'name' => $name,
'type' => $type,
@ -1078,12 +1113,8 @@ class Parser
$description = $this->parseDescription();
$this->expectKeyword('interface');
$name = $this->parseName();
$directives = $this->parseDirectives();
$fields = $this->any(
Token::BRACE_L,
[$this, 'parseFieldDefinition'],
Token::BRACE_R
);
$directives = $this->parseDirectives(true);
$fields = $this->parseFieldDefinitions();
return new InterfaceTypeDefinitionNode([
'name' => $name,
@ -1104,7 +1135,7 @@ class Parser
$description = $this->parseDescription();
$this->expectKeyword('union');
$name = $this->parseName();
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
$this->expect(Token::EQUALS);
$types = $this->parseUnionMembers();
@ -1146,7 +1177,7 @@ class Parser
$description = $this->parseDescription();
$this->expectKeyword('enum');
$name = $this->parseName();
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
$values = $this->many(
Token::BRACE_L,
[$this, 'parseEnumValueDefinition'],
@ -1164,13 +1195,14 @@ class Parser
/**
* @return EnumValueDefinitionNode
* @throws SyntaxError
*/
function parseEnumValueDefinition()
{
$start = $this->lexer->token;
$description = $this->parseDescription();
$name = $this->parseName();
$directives = $this->parseDirectives();
$directives = $this->parseDirectives(true);
return new EnumValueDefinitionNode([
'name' => $name,
@ -1190,8 +1222,8 @@ class Parser
$description = $this->parseDescription();
$this->expectKeyword('input');
$name = $this->parseName();
$directives = $this->parseDirectives();
$fields = $this->any(
$directives = $this->parseDirectives(true);
$fields = $this->many(
Token::BRACE_L,
[$this, 'parseInputValueDef'],
Token::BRACE_R
@ -1207,17 +1239,51 @@ class Parser
}
/**
* @return TypeExtensionDefinitionNode
* @return TypeExtensionNode
* @throws SyntaxError
*/
function parseTypeExtensionDefinition()
function parseTypeExtension()
{
$keywordToken = $this->lexer->lookahead();
if ($keywordToken->kind === Token::NAME) {
switch ($keywordToken->value) {
case 'type':
return $this->parseObjectTypeExtension();
}
}
throw $this->unexpected($keywordToken);
}
/**
* @return ObjectTypeExtensionNode
* @throws SyntaxError
*/
function parseObjectTypeExtension() {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$definition = $this->parseObjectTypeDefinition();
$this->expectKeyword('type');
$name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives(true);
$fields = $this->peek(Token::BRACE_L)
? $this->parseFieldDefinitions()
: [];
return new TypeExtensionDefinitionNode([
'definition' => $definition,
if (
count($interfaces) === 0 &&
count($directives) === 0 &&
count($fields) === 0
) {
throw $this->unexpected();
}
return new ObjectTypeExtensionNode([
'name' => $name,
'interfaces' => $interfaces,
'directives' => $directives,
'fields' => $fields,
'loc' => $this->loc($start)
]);
}
@ -1251,6 +1317,7 @@ class Parser
/**
* @return NameNode[]
* @throws SyntaxError
*/
function parseDirectiveLocations()
{
@ -1258,8 +1325,23 @@ class Parser
$this->skip(Token::PIPE);
$locations = [];
do {
$locations[] = $this->parseName();
$locations[] = $this->parseDirectiveLocation();
} while ($this->skip(Token::PIPE));
return $locations;
}
/**
* @return NameNode
* @throws SyntaxError
*/
function parseDirectiveLocation()
{
$start = $this->lexer->token;
$name = $this->parseName();
if (DirectiveLocation::has($name->value)) {
return $name;
}
throw $this->unexpected($start);
}
}

View File

@ -35,7 +35,7 @@ use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Utils\Utils;
@ -278,8 +278,14 @@ class Printer
], ' ')
], "\n");
},
NodeKind::TYPE_EXTENSION_DEFINITION => function(TypeExtensionDefinitionNode $def) {
return "extend {$def->definition}";
NodeKind::OBJECT_TYPE_EXTENSION => function(ObjectTypeExtensionNode $def) {
return $this->join([
'extend type',
$def->name,
$this->wrap('implements ', $this->join($def->interfaces, ', ')),
$this->join($def->directives, ' '),
$this->block($def->fields),
], ' ');
},
NodeKind::DIRECTIVE_DEFINITION => function(DirectiveDefinitionNode $def) {
return $this->join([
@ -309,7 +315,7 @@ class Printer
{
return ($array && $this->length($array))
? "{\n" . $this->indent($this->join($array, "\n")) . "\n}"
: '{}';
: '';
}
public function indent($maybeString)

View File

@ -142,7 +142,7 @@ class Visitor
NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
NodeKind::TYPE_EXTENSION_DEFINITION => [ 'definition' ],
NodeKind::OBJECT_TYPE_EXTENSION => [ 'name', 'interfaces', 'directives', 'fields' ],
NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations']
];

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\DirectiveLocation;
/**
* Class Directive
@ -18,35 +19,6 @@ class Directive
// Schema Definitions
/**
* @var array
* @deprecated as of 8.0 (use DirectiveLocation constants directly)
*/
public static $directiveLocations = [
// Operations:
DirectiveLocation::QUERY => DirectiveLocation::QUERY,
DirectiveLocation::MUTATION => DirectiveLocation::MUTATION,
DirectiveLocation::SUBSCRIPTION => DirectiveLocation::SUBSCRIPTION,
DirectiveLocation::FIELD => DirectiveLocation::FIELD,
DirectiveLocation::FRAGMENT_DEFINITION => DirectiveLocation::FRAGMENT_DEFINITION,
DirectiveLocation::FRAGMENT_SPREAD => DirectiveLocation::FRAGMENT_SPREAD,
DirectiveLocation::INLINE_FRAGMENT => DirectiveLocation::INLINE_FRAGMENT,
// Schema Definitions
DirectiveLocation::SCHEMA => DirectiveLocation::SCHEMA,
DirectiveLocation::SCALAR => DirectiveLocation::SCALAR,
DirectiveLocation::OBJECT => DirectiveLocation::OBJECT,
DirectiveLocation::FIELD_DEFINITION => DirectiveLocation::FIELD_DEFINITION,
DirectiveLocation::ARGUMENT_DEFINITION => DirectiveLocation::ARGUMENT_DEFINITION,
DirectiveLocation::IFACE => DirectiveLocation::IFACE,
DirectiveLocation::UNION => DirectiveLocation::UNION,
DirectiveLocation::ENUM => DirectiveLocation::ENUM,
DirectiveLocation::ENUM_VALUE => DirectiveLocation::ENUM_VALUE,
DirectiveLocation::INPUT_OBJECT => DirectiveLocation::INPUT_OBJECT,
DirectiveLocation::INPUT_FIELD_DEFINITION => DirectiveLocation::INPUT_FIELD_DEFINITION
];
/**
* @return Directive
*/

View File

@ -1,27 +0,0 @@
<?php
namespace GraphQL\Type\Definition;
/**
* List of available directive locations
*/
class DirectiveLocation
{
const IFACE = 'INTERFACE';
const SUBSCRIPTION = 'SUBSCRIPTION';
const FRAGMENT_SPREAD = 'FRAGMENT_SPREAD';
const QUERY = 'QUERY';
const MUTATION = 'MUTATION';
const FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION';
const INPUT_OBJECT = 'INPUT_OBJECT';
const INLINE_FRAGMENT = 'INLINE_FRAGMENT';
const UNION = 'UNION';
const SCALAR = 'SCALAR';
const FIELD_DEFINITION = 'FIELD_DEFINITION';
const ARGUMENT_DEFINITION = 'ARGUMENT_DEFINITION';
const ENUM = 'ENUM';
const OBJECT = 'OBJECT';
const ENUM_VALUE = 'ENUM_VALUE';
const FIELD = 'FIELD';
const SCHEMA = 'SCHEMA';
const INPUT_FIELD_DEFINITION = 'INPUT_FIELD_DEFINITION';
}

View File

@ -3,7 +3,7 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Utils\Utils;
@ -70,7 +70,7 @@ class ObjectType extends Type implements OutputType, CompositeType
public $astNode;
/**
* @var TypeExtensionDefinitionNode[]
* @var ObjectTypeExtensionNode[]
*/
public $extensionASTNodes;

View File

@ -1,10 +1,9 @@
<?php
namespace GraphQL\Type;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\DirectiveLocation;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\FieldDefinition;
@ -602,7 +601,7 @@ EOD;
],
'type' => [
'type' => Type::nonNull(self::_type()),
'resolve' => function ($field) {
'resolve' => function (FieldDefinition $field) {
return $field->getType();
}
],

View File

@ -30,7 +30,6 @@ use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Introspection;
class ASTDefinitionBuilder
{

View File

@ -22,6 +22,7 @@ use GraphQL\Validator\Rules\AbstractValidationRule;
use GraphQL\Validator\Rules\ArgumentsOfCorrectType;
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\Rules\ExecutableDefinitions;
use GraphQL\Validator\Rules\FieldsOnCorrectType;
use GraphQL\Validator\Rules\FragmentsOnCompositeTypes;
use GraphQL\Validator\Rules\KnownArgumentNames;
@ -122,6 +123,7 @@ class DocumentValidator
{
if (null === self::$defaultRules) {
self::$defaultRules = [
ExecutableDefinitions::class => new ExecutableDefinitions(),
UniqueOperationNames::class => new UniqueOperationNames(),
LoneAnonymousOperation::class => new LoneAnonymousOperation(),
KnownTypeNames::class => new KnownTypeNames(),

View File

@ -0,0 +1,47 @@
<?php
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Validator\ValidationContext;
/**
* Executable definitions
*
* A GraphQL document is only valid for execution if all definitions are either
* operation or fragment definitions.
*/
class ExecutableDefinitions extends AbstractValidationRule
{
static function nonExecutableDefinitionMessage($defName)
{
return "The \"$defName\" definition is not executable.";
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::DOCUMENT => function (DocumentNode $node) use ($context) {
/** @var Node $definition */
foreach ($node->definitions as $definition) {
if (
!$definition instanceof OperationDefinitionNode &&
!$definition instanceof FragmentDefinitionNode
) {
$context->reportError(new Error(
self::nonExecutableDefinitionMessage($definition->name->value),
[$definition->name]
));
}
}
return Visitor::skipNode();
}
];
}
}

View File

@ -5,8 +5,8 @@ use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext;
use GraphQL\Type\Definition\DirectiveLocation;
class KnownDirectives extends AbstractValidationRule
{
@ -37,7 +37,7 @@ class KnownDirectives extends AbstractValidationRule
self::unknownDirectiveMessage($node->name->value),
[$node]
));
return ;
return;
}
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
@ -73,7 +73,8 @@ class KnownDirectives extends AbstractValidationRule
case NodeKind::FRAGMENT_DEFINITION: return DirectiveLocation::FRAGMENT_DEFINITION;
case NodeKind::SCHEMA_DEFINITION: return DirectiveLocation::SCHEMA;
case NodeKind::SCALAR_TYPE_DEFINITION: return DirectiveLocation::SCALAR;
case NodeKind::OBJECT_TYPE_DEFINITION: return DirectiveLocation::OBJECT;
case NodeKind::OBJECT_TYPE_DEFINITION:
case NodeKind::OBJECT_TYPE_EXTENSION: return DirectiveLocation::OBJECT;
case NodeKind::FIELD_DEFINITION: return DirectiveLocation::FIELD_DEFINITION;
case NodeKind::INTERFACE_TYPE_DEFINITION: return DirectiveLocation::IFACE;
case NodeKind::UNION_TYPE_DEFINITION: return DirectiveLocation::UNION;

View File

@ -965,14 +965,14 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
}
/**
* @it fails to execute a query containing a type definition
* @it executes ignoring invalid non-executable definitions
*/
public function testFailsToExecuteQueryContainingTypeDefinition()
public function testExecutesIgnoringInvalidNonExecutableDefinitions()
{
$query = Parser::parse('
{ foo }
type Query { foo: String }
type Query { bar: String }
');
$schema = new Schema([
@ -988,12 +988,9 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
$result = Executor::execute($schema, $query);
$expected = [
'errors' => [
[
'message' => 'GraphQL cannot execute a request containing a ObjectTypeDefinition.',
'locations' => [['line' => 4, 'column' => 7]],
]
]
'data' => [
'foo' => null,
],
];
$this->assertArraySubset($expected, $result->toArray());

View File

@ -150,9 +150,7 @@ extend type Hello {
'kind' => NodeKind::DOCUMENT,
'definitions' => [
[
'kind' => NodeKind::TYPE_EXTENSION_DEFINITION,
'definition' => [
'kind' => NodeKind::OBJECT_TYPE_DEFINITION,
'kind' => NodeKind::OBJECT_TYPE_EXTENSION,
'name' => $this->nameNode('Hello', $loc(13, 18)),
'interfaces' => [],
'directives' => [],
@ -163,9 +161,6 @@ extend type Hello {
$loc(23, 36)
)
],
'loc' => $loc(8, 38),
'description' => null
],
'loc' => $loc(1, 38)
]
],
@ -174,16 +169,59 @@ extend type Hello {
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
/**
* @it Extension without fields
*/
public function testExtensionWithoutFields()
{
$body = 'extend type Hello implements Greeting';
$doc = Parser::parse($body);
$loc = function($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'definitions' => [
[
'kind' => NodeKind::OBJECT_TYPE_EXTENSION,
'name' => $this->nameNode('Hello', $loc(12, 17)),
'interfaces' => [
$this->typeNode('Greeting', $loc(29, 37)),
],
'directives' => [],
'fields' => [],
'loc' => $loc(0, 37)
]
],
'loc' => $loc(0, 37)
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
/**
* @it Extension do not include descriptions
* @expectedException \GraphQL\Error\SyntaxError
* @expectedExceptionMessage Syntax Error GraphQL (2:1)
* @expectedExceptionMessage Syntax Error GraphQL (3:7)
*/
public function testExtensionDoNotIncludeDescriptions() {
$body = '
"Description"
extend type Hello {
"Description"
extend type Hello {
world: String
}';
Parser::parse($body);
}
/**
* @it Extension do not include descriptions
* @expectedException \GraphQL\Error\SyntaxError
* @expectedExceptionMessage Syntax Error GraphQL (2:14)
*/
public function testExtensionDoNotIncludeDescriptions2() {
$body = '
extend "Description" type Hello {
world: String
}
}';
Parser::parse($body);
}
@ -236,7 +274,7 @@ type Hello {
*/
public function testSimpleTypeInheritingInterface()
{
$body = 'type Hello implements World { }';
$body = 'type Hello implements World { field: String }';
$loc = function($start, $end) { return TestUtils::locArray($start, $end); };
$doc = Parser::parse($body);
@ -250,12 +288,18 @@ type Hello {
$this->typeNode('World', $loc(22, 27))
],
'directives' => [],
'fields' => [],
'loc' => $loc(0,31),
'fields' => [
$this->fieldNode(
$this->nameNode('field', $loc(30, 35)),
$this->typeNode('String', $loc(37, 43)),
$loc(30, 43)
)
],
'loc' => $loc(0, 45),
'description' => null
]
],
'loc' => $loc(0,31)
'loc' => $loc(0, 45)
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -266,7 +310,7 @@ type Hello {
*/
public function testSimpleTypeInheritingMultipleInterfaces()
{
$body = 'type Hello implements Wo, rld { }';
$body = 'type Hello implements Wo, rld { field: String }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body);
@ -281,12 +325,18 @@ type Hello {
$this->typeNode('rld', $loc(26,29))
],
'directives' => [],
'fields' => [],
'loc' => $loc(0, 33),
'fields' => [
$this->fieldNode(
$this->nameNode('field', $loc(32, 37)),
$this->typeNode('String', $loc(39, 45)),
$loc(32, 45)
)
],
'loc' => $loc(0, 47),
'description' => null
]
],
'loc' => $loc(0, 33)
'loc' => $loc(0, 47)
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -754,6 +804,7 @@ input Hello {
/**
* @it Simple input object with args should fail
* @expectedException \GraphQL\Error\SyntaxError
*/
public function testSimpleInputObjectWithArgsShouldFail()
{
@ -761,7 +812,19 @@ input Hello {
input Hello {
world(foo: Int): String
}';
$this->setExpectedException('GraphQL\Error\SyntaxError');
Parser::parse($body);
}
/**
* @it Directive with incorrect locations
* @expectedException \GraphQL\Error\SyntaxError
* @expectedExceptionMessage Syntax Error GraphQL (2:33) Unexpected Name "INCORRECT_LOCATION"
*/
public function testDirectiveWithIncorrectLocationShouldFail()
{
$body = '
directive @foo on FIELD | INCORRECT_LOCATION
';
Parser::parse($body);
}

View File

@ -116,9 +116,7 @@ extend type Foo {
seven(argument: [String]): Type
}
extend type Foo @onType {}
type NoFields {}
extend type Foo @onType
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

View File

@ -68,9 +68,7 @@ extend type Foo {
seven(argument: [String]): Type
}
extend type Foo @onType {}
type NoFields {}
extend type Foo @onType
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

View File

@ -1025,7 +1025,9 @@ schema {
query: Hello
}
type Hello implements Bar { }
type Hello implements Bar {
field: String
}
';
$doc = Parser::parse($body);
$schema = BuildSchema::buildAST($doc);

View File

@ -0,0 +1,79 @@
<?php
namespace GraphQL\Tests\Validator;
use GraphQL\Error\FormattedError;
use GraphQL\Language\SourceLocation;
use GraphQL\Validator\Rules\ExecutableDefinitions;
use GraphQL\Validator\Rules\KnownDirectives;
class ExecutableDefinitionsTest extends TestCase
{
// Validate: Executable definitions
/**
* @it with only operation
*/
public function testWithOnlyOperation()
{
$this->expectPassesRule(new ExecutableDefinitions, '
query Foo {
dog {
name
}
}
');
}
/**
* @it with operation and fragment
*/
public function testWithOperationAndFragment()
{
$this->expectPassesRule(new ExecutableDefinitions, '
query Foo {
dog {
name
...Frag
}
}
fragment Frag on Dog {
name
}
');
}
/**
* @it with typeDefinition
*/
public function testWithTypeDefinition()
{
$this->expectFailsRule(new ExecutableDefinitions, '
query Foo {
dog {
name
}
}
type Cow {
name: String
}
extend type Dog {
color: String
}
',
[
$this->nonExecutableDefinition('Cow', 8, 12),
$this->nonExecutableDefinition('Dog', 12, 19),
]);
}
private function nonExecutableDefinition($defName, $line, $column)
{
return FormattedError::create(
ExecutableDefinitions::nonExecutableDefinitionMessage($defName),
[ new SourceLocation($line, $column) ]
);
}
}

View File

@ -136,6 +136,8 @@ class KnownDirectivesTest extends TestCase
myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition
}
extend type MyObj @onObject
scalar MyScalar @onScalar
interface MyInterface @onInterface {

View File

@ -3,6 +3,7 @@ namespace GraphQL\Tests\Validator;
use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
@ -259,6 +260,24 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
]
]);
$invalidScalar = new CustomScalarType([
'name' => 'Invalid',
'serialize' => function ($value) { return $value; },
'parseLiteral' => function ($node) {
throw new \Exception('Invalid scalar is always invalid: ' . $node->value);
},
'parseValue' => function ($value) {
throw new \Exception('Invalid scalar is always invalid: ' . $value);
},
]);
$anyScalar = new CustomScalarType([
'name' => 'Any',
'serialize' => function ($value) { return $value; },
'parseLiteral' => function ($node) { return $node; }, // Allows any value
'parseValue' => function ($value) { return $value; }, // Allows any value
]);
$queryRoot = new ObjectType([
'name' => 'QueryRoot',
'fields' => [
@ -274,6 +293,14 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
'dogOrHuman' => ['type' => $DogOrHuman],
'humanOrAlien' => ['type' => $HumanOrAlien],
'complicatedArgs' => ['type' => $ComplicatedArgs],
'invalidArg' => [
'args' => ['arg' => ['type' => $invalidScalar]],
'type' => Type::string(),
],
'anyArg' => [
'args' => ['arg' => ['type' => $anyScalar]],
'type' => Type::string(),
],
]
]);

View File

@ -9,7 +9,7 @@ $entries = [
\GraphQL\GraphQL::class,
\GraphQL\Type\Definition\Type::class,
\GraphQL\Type\Definition\ResolveInfo::class,
\GraphQL\Type\Definition\DirectiveLocation::class => ['constants' => true],
\GraphQL\Language\DirectiveLocation::class => ['constants' => true],
\GraphQL\Type\SchemaConfig::class,
\GraphQL\Type\Schema::class,
\GraphQL\Language\Parser::class,