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) function getFieldSelection($depth = 0)
``` ```
# GraphQL\Type\Definition\DirectiveLocation # GraphQL\Language\DirectiveLocation
List of available directive locations List of available directive locations
**Class Constants:** **Class Constants:**
```php ```php
const IFACE = "INTERFACE";
const SUBSCRIPTION = "SUBSCRIPTION";
const FRAGMENT_SPREAD = "FRAGMENT_SPREAD";
const QUERY = "QUERY"; const QUERY = "QUERY";
const MUTATION = "MUTATION"; const MUTATION = "MUTATION";
const SUBSCRIPTION = "SUBSCRIPTION";
const FIELD = "FIELD";
const FRAGMENT_DEFINITION = "FRAGMENT_DEFINITION"; const FRAGMENT_DEFINITION = "FRAGMENT_DEFINITION";
const INPUT_OBJECT = "INPUT_OBJECT"; const FRAGMENT_SPREAD = "FRAGMENT_SPREAD";
const INLINE_FRAGMENT = "INLINE_FRAGMENT"; const INLINE_FRAGMENT = "INLINE_FRAGMENT";
const UNION = "UNION"; const SCHEMA = "SCHEMA";
const SCALAR = "SCALAR"; const SCALAR = "SCALAR";
const OBJECT = "OBJECT";
const FIELD_DEFINITION = "FIELD_DEFINITION"; const FIELD_DEFINITION = "FIELD_DEFINITION";
const ARGUMENT_DEFINITION = "ARGUMENT_DEFINITION"; const ARGUMENT_DEFINITION = "ARGUMENT_DEFINITION";
const IFACE = "INTERFACE";
const UNION = "UNION";
const ENUM = "ENUM"; const ENUM = "ENUM";
const OBJECT = "OBJECT";
const ENUM_VALUE = "ENUM_VALUE"; const ENUM_VALUE = "ENUM_VALUE";
const FIELD = "FIELD"; const INPUT_OBJECT = "INPUT_OBJECT";
const SCHEMA = "SCHEMA";
const INPUT_FIELD_DEFINITION = "INPUT_FIELD_DEFINITION"; const INPUT_FIELD_DEFINITION = "INPUT_FIELD_DEFINITION";
``` ```
@ -936,7 +936,7 @@ const UNION_TYPE_DEFINITION = "UnionTypeDefinition";
const ENUM_TYPE_DEFINITION = "EnumTypeDefinition"; const ENUM_TYPE_DEFINITION = "EnumTypeDefinition";
const ENUM_VALUE_DEFINITION = "EnumValueDefinition"; const ENUM_VALUE_DEFINITION = "EnumValueDefinition";
const INPUT_OBJECT_TYPE_DEFINITION = "InputObjectTypeDefinition"; const INPUT_OBJECT_TYPE_DEFINITION = "InputObjectTypeDefinition";
const TYPE_EXTENSION_DEFINITION = "TypeExtensionDefinition"; const OBJECT_TYPE_EXTENSION = "ObjectTypeExtension";
const DIRECTIVE_DEFINITION = "DirectiveDefinition"; 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
<?php <?php
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\DirectiveLocation;
use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\FieldArgument;
$trackDirective = new Directive([ $trackDirective = new Directive([

View File

@ -211,11 +211,6 @@ class Executor
case NodeKind::FRAGMENT_DEFINITION: case NodeKind::FRAGMENT_DEFINITION:
$fragments[$definition->name->value] = $definition; $fragments[$definition->name->value] = $definition;
break; 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 // Type Extensions
const TYPE_EXTENSION_DEFINITION = 'TypeExtensionDefinition'; const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
// Directive Definitions // Directive Definitions
@ -127,7 +127,7 @@ class NodeKind
NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class, NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
// Type Extensions // Type Extensions
NodeKind::TYPE_EXTENSION_DEFINITION => TypeExtensionDefinitionNode::class, NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
// Directive Definitions // Directive Definitions
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class 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 interface TypeSystemDefinitionNode extends DefinitionNode
{ {
/** /**
export type TypeSystemDefinitionNode = SchemaDefinitionNode export type TypeSystemDefinitionNode =
| SchemaDefinitionNode
| TypeDefinitionNode | TypeDefinitionNode
| TypeExtensionDefinitionNode | TypeExtensionNode
| DirectiveDefinitionNode | 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\SchemaDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode; 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\TypeSystemDefinitionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\VariableNode; use GraphQL\Language\AST\VariableNode;
@ -393,7 +394,7 @@ class Parser
'operation' => $operation, 'operation' => $operation,
'name' => $name, 'name' => $name,
'variableDefinitions' => $this->parseVariableDefinitions(), 'variableDefinitions' => $this->parseVariableDefinitions(),
'directives' => $this->parseDirectives(), 'directives' => $this->parseDirectives(false),
'selectionSet' => $this->parseSelectionSet(), 'selectionSet' => $this->parseSelectionSet(),
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -494,6 +495,7 @@ class Parser
/** /**
* @return FieldNode * @return FieldNode
* @throws SyntaxError
*/ */
function parseField() function parseField()
{ {
@ -511,20 +513,23 @@ class Parser
return new FieldNode([ return new FieldNode([
'alias' => $alias, 'alias' => $alias,
'name' => $name, 'name' => $name,
'arguments' => $this->parseArguments(), 'arguments' => $this->parseArguments(false),
'directives' => $this->parseDirectives(), 'directives' => $this->parseDirectives(false),
'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null, 'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
/** /**
* @param bool $isConst
* @return ArgumentNode[]|NodeList * @return ArgumentNode[]|NodeList
* @throws SyntaxError
*/ */
function parseArguments() function parseArguments($isConst)
{ {
$item = $isConst ? 'parseConstArgument' : 'parseArgument';
return $this->peek(Token::PAREN_L) ? 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([]); 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. // Implements the parsing rules in the Fragments section.
/** /**
@ -561,7 +585,7 @@ class Parser
if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') { if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') {
return new FragmentSpreadNode([ return new FragmentSpreadNode([
'name' => $this->parseFragmentName(), 'name' => $this->parseFragmentName(),
'directives' => $this->parseDirectives(), 'directives' => $this->parseDirectives(false),
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
@ -574,7 +598,7 @@ class Parser
return new InlineFragmentNode([ return new InlineFragmentNode([
'typeCondition' => $typeCondition, 'typeCondition' => $typeCondition,
'directives' => $this->parseDirectives(), 'directives' => $this->parseDirectives(false),
'selectionSet' => $this->parseSelectionSet(), 'selectionSet' => $this->parseSelectionSet(),
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -596,7 +620,7 @@ class Parser
return new FragmentDefinitionNode([ return new FragmentDefinitionNode([
'name' => $name, 'name' => $name,
'typeCondition' => $typeCondition, 'typeCondition' => $typeCondition,
'directives' => $this->parseDirectives(), 'directives' => $this->parseDirectives(false),
'selectionSet' => $this->parseSelectionSet(), 'selectionSet' => $this->parseSelectionSet(),
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -775,28 +799,31 @@ class Parser
// Implements the parsing rules in the Directives section. // Implements the parsing rules in the Directives section.
/** /**
* @param bool $isConst
* @return DirectiveNode[]|NodeList * @return DirectiveNode[]|NodeList
* @throws SyntaxError
*/ */
function parseDirectives() function parseDirectives($isConst)
{ {
$directives = []; $directives = [];
while ($this->peek(Token::AT)) { while ($this->peek(Token::AT)) {
$directives[] = $this->parseDirective(); $directives[] = $this->parseDirective($isConst);
} }
return new NodeList($directives); return new NodeList($directives);
} }
/** /**
* @param bool $isConst
* @return DirectiveNode * @return DirectiveNode
* @throws SyntaxError * @throws SyntaxError
*/ */
function parseDirective() function parseDirective($isConst)
{ {
$start = $this->lexer->token; $start = $this->lexer->token;
$this->expect(Token::AT); $this->expect(Token::AT);
return new DirectiveNode([ return new DirectiveNode([
'name' => $this->parseName(), 'name' => $this->parseName(),
'arguments' => $this->parseArguments(), 'arguments' => $this->parseArguments($isConst),
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
@ -849,7 +876,7 @@ class Parser
* TypeSystemDefinition : * TypeSystemDefinition :
* - SchemaDefinition * - SchemaDefinition
* - TypeDefinition * - TypeDefinition
* - TypeExtensionDefinition * - TypeExtension
* - DirectiveDefinition * - DirectiveDefinition
* *
* TypeDefinition : * TypeDefinition :
@ -879,12 +906,12 @@ class Parser
case 'union': return $this->parseUnionTypeDefinition(); case 'union': return $this->parseUnionTypeDefinition();
case 'enum': return $this->parseEnumTypeDefinition(); case 'enum': return $this->parseEnumTypeDefinition();
case 'input': return $this->parseInputObjectTypeDefinition(); case 'input': return $this->parseInputObjectTypeDefinition();
case 'extend': return $this->parseTypeExtensionDefinition(); case 'extend': return $this->parseTypeExtension();
case 'directive': return $this->parseDirectiveDefinition(); case 'directive': return $this->parseDirectiveDefinition();
} }
} }
throw $this->unexpected(); throw $this->unexpected($keywordToken);
} }
/** /**
@ -911,7 +938,7 @@ class Parser
{ {
$start = $this->lexer->token; $start = $this->lexer->token;
$this->expectKeyword('schema'); $this->expectKeyword('schema');
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
$operationTypes = $this->many( $operationTypes = $this->many(
Token::BRACE_L, Token::BRACE_L,
@ -953,7 +980,7 @@ class Parser
$description = $this->parseDescription(); $description = $this->parseDescription();
$this->expectKeyword('scalar'); $this->expectKeyword('scalar');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
return new ScalarTypeDefinitionNode([ return new ScalarTypeDefinitionNode([
'name' => $name, 'name' => $name,
@ -974,13 +1001,8 @@ class Parser
$this->expectKeyword('type'); $this->expectKeyword('type');
$name = $this->parseName(); $name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces(); $interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
$fields = $this->parseFieldDefinitions();
$fields = $this->any(
Token::BRACE_L,
[$this, 'parseFieldDefinition'],
Token::BRACE_R
);
return new ObjectTypeDefinitionNode([ return new ObjectTypeDefinitionNode([
'name' => $name, 'name' => $name,
@ -1007,6 +1029,19 @@ class Parser
return $types; return $types;
} }
/**
* @return FieldDefinitionNode[]|NodeList
* @throws SyntaxError
*/
function parseFieldDefinitions()
{
return $this->many(
Token::BRACE_L,
[$this, 'parseFieldDefinition'],
Token::BRACE_R
);
}
/** /**
* @return FieldDefinitionNode * @return FieldDefinitionNode
* @throws SyntaxError * @throws SyntaxError
@ -1019,7 +1054,7 @@ class Parser
$args = $this->parseArgumentDefs(); $args = $this->parseArgumentDefs();
$this->expect(Token::COLON); $this->expect(Token::COLON);
$type = $this->parseTypeReference(); $type = $this->parseTypeReference();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
return new FieldDefinitionNode([ return new FieldDefinitionNode([
'name' => $name, 'name' => $name,
@ -1057,7 +1092,7 @@ class Parser
if ($this->skip(Token::EQUALS)) { if ($this->skip(Token::EQUALS)) {
$defaultValue = $this->parseConstValue(); $defaultValue = $this->parseConstValue();
} }
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
return new InputValueDefinitionNode([ return new InputValueDefinitionNode([
'name' => $name, 'name' => $name,
'type' => $type, 'type' => $type,
@ -1078,12 +1113,8 @@ class Parser
$description = $this->parseDescription(); $description = $this->parseDescription();
$this->expectKeyword('interface'); $this->expectKeyword('interface');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
$fields = $this->any( $fields = $this->parseFieldDefinitions();
Token::BRACE_L,
[$this, 'parseFieldDefinition'],
Token::BRACE_R
);
return new InterfaceTypeDefinitionNode([ return new InterfaceTypeDefinitionNode([
'name' => $name, 'name' => $name,
@ -1104,7 +1135,7 @@ class Parser
$description = $this->parseDescription(); $description = $this->parseDescription();
$this->expectKeyword('union'); $this->expectKeyword('union');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
$this->expect(Token::EQUALS); $this->expect(Token::EQUALS);
$types = $this->parseUnionMembers(); $types = $this->parseUnionMembers();
@ -1146,7 +1177,7 @@ class Parser
$description = $this->parseDescription(); $description = $this->parseDescription();
$this->expectKeyword('enum'); $this->expectKeyword('enum');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
$values = $this->many( $values = $this->many(
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseEnumValueDefinition'], [$this, 'parseEnumValueDefinition'],
@ -1164,13 +1195,14 @@ class Parser
/** /**
* @return EnumValueDefinitionNode * @return EnumValueDefinitionNode
* @throws SyntaxError
*/ */
function parseEnumValueDefinition() function parseEnumValueDefinition()
{ {
$start = $this->lexer->token; $start = $this->lexer->token;
$description = $this->parseDescription(); $description = $this->parseDescription();
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
return new EnumValueDefinitionNode([ return new EnumValueDefinitionNode([
'name' => $name, 'name' => $name,
@ -1190,8 +1222,8 @@ class Parser
$description = $this->parseDescription(); $description = $this->parseDescription();
$this->expectKeyword('input'); $this->expectKeyword('input');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(); $directives = $this->parseDirectives(true);
$fields = $this->any( $fields = $this->many(
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseInputValueDef'], [$this, 'parseInputValueDef'],
Token::BRACE_R Token::BRACE_R
@ -1207,17 +1239,51 @@ class Parser
} }
/** /**
* @return TypeExtensionDefinitionNode * @return TypeExtensionNode
* @throws SyntaxError * @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; $start = $this->lexer->token;
$this->expectKeyword('extend'); $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([ if (
'definition' => $definition, 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) 'loc' => $this->loc($start)
]); ]);
} }
@ -1251,6 +1317,7 @@ class Parser
/** /**
* @return NameNode[] * @return NameNode[]
* @throws SyntaxError
*/ */
function parseDirectiveLocations() function parseDirectiveLocations()
{ {
@ -1258,8 +1325,23 @@ class Parser
$this->skip(Token::PIPE); $this->skip(Token::PIPE);
$locations = []; $locations = [];
do { do {
$locations[] = $this->parseName(); $locations[] = $this->parseDirectiveLocation();
} while ($this->skip(Token::PIPE)); } while ($this->skip(Token::PIPE));
return $locations; 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\SchemaDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\TypeExtensionDefinitionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
@ -278,8 +278,14 @@ class Printer
], ' ') ], ' ')
], "\n"); ], "\n");
}, },
NodeKind::TYPE_EXTENSION_DEFINITION => function(TypeExtensionDefinitionNode $def) { NodeKind::OBJECT_TYPE_EXTENSION => function(ObjectTypeExtensionNode $def) {
return "extend {$def->definition}"; 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) { NodeKind::DIRECTIVE_DEFINITION => function(DirectiveDefinitionNode $def) {
return $this->join([ return $this->join([
@ -309,7 +315,7 @@ class Printer
{ {
return ($array && $this->length($array)) return ($array && $this->length($array))
? "{\n" . $this->indent($this->join($array, "\n")) . "\n}" ? "{\n" . $this->indent($this->join($array, "\n")) . "\n}"
: '{}'; : '';
} }
public function indent($maybeString) public function indent($maybeString)

View File

@ -142,7 +142,7 @@ class Visitor
NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'], NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'], NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'], 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'] NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations']
]; ];

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\DirectiveLocation;
/** /**
* Class Directive * Class Directive
@ -18,35 +19,6 @@ class Directive
// Schema Definitions // 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 * @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\Error\InvariantViolation;
use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\TypeExtensionDefinitionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
@ -70,7 +70,7 @@ class ObjectType extends Type implements OutputType, CompositeType
public $astNode; public $astNode;
/** /**
* @var TypeExtensionDefinitionNode[] * @var ObjectTypeExtensionNode[]
*/ */
public $extensionASTNodes; public $extensionASTNodes;

View File

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

View File

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

View File

@ -22,6 +22,7 @@ use GraphQL\Validator\Rules\AbstractValidationRule;
use GraphQL\Validator\Rules\ArgumentsOfCorrectType; use GraphQL\Validator\Rules\ArgumentsOfCorrectType;
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType; use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
use GraphQL\Validator\Rules\DisableIntrospection; use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\Rules\ExecutableDefinitions;
use GraphQL\Validator\Rules\FieldsOnCorrectType; use GraphQL\Validator\Rules\FieldsOnCorrectType;
use GraphQL\Validator\Rules\FragmentsOnCompositeTypes; use GraphQL\Validator\Rules\FragmentsOnCompositeTypes;
use GraphQL\Validator\Rules\KnownArgumentNames; use GraphQL\Validator\Rules\KnownArgumentNames;
@ -122,6 +123,7 @@ class DocumentValidator
{ {
if (null === self::$defaultRules) { if (null === self::$defaultRules) {
self::$defaultRules = [ self::$defaultRules = [
ExecutableDefinitions::class => new ExecutableDefinitions(),
UniqueOperationNames::class => new UniqueOperationNames(), UniqueOperationNames::class => new UniqueOperationNames(),
LoneAnonymousOperation::class => new LoneAnonymousOperation(), LoneAnonymousOperation::class => new LoneAnonymousOperation(),
KnownTypeNames::class => new KnownTypeNames(), 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\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use GraphQL\Type\Definition\DirectiveLocation;
class KnownDirectives extends AbstractValidationRule class KnownDirectives extends AbstractValidationRule
{ {
@ -37,7 +37,7 @@ class KnownDirectives extends AbstractValidationRule
self::unknownDirectiveMessage($node->name->value), self::unknownDirectiveMessage($node->name->value),
[$node] [$node]
)); ));
return ; return;
} }
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors); $candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
@ -73,7 +73,8 @@ class KnownDirectives extends AbstractValidationRule
case NodeKind::FRAGMENT_DEFINITION: return DirectiveLocation::FRAGMENT_DEFINITION; case NodeKind::FRAGMENT_DEFINITION: return DirectiveLocation::FRAGMENT_DEFINITION;
case NodeKind::SCHEMA_DEFINITION: return DirectiveLocation::SCHEMA; case NodeKind::SCHEMA_DEFINITION: return DirectiveLocation::SCHEMA;
case NodeKind::SCALAR_TYPE_DEFINITION: return DirectiveLocation::SCALAR; 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::FIELD_DEFINITION: return DirectiveLocation::FIELD_DEFINITION;
case NodeKind::INTERFACE_TYPE_DEFINITION: return DirectiveLocation::IFACE; case NodeKind::INTERFACE_TYPE_DEFINITION: return DirectiveLocation::IFACE;
case NodeKind::UNION_TYPE_DEFINITION: return DirectiveLocation::UNION; 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(' $query = Parser::parse('
{ foo } { foo }
type Query { foo: String } type Query { bar: String }
'); ');
$schema = new Schema([ $schema = new Schema([
@ -988,12 +988,9 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
$result = Executor::execute($schema, $query); $result = Executor::execute($schema, $query);
$expected = [ $expected = [
'errors' => [ 'data' => [
[ 'foo' => null,
'message' => 'GraphQL cannot execute a request containing a ObjectTypeDefinition.', ],
'locations' => [['line' => 4, 'column' => 7]],
]
]
]; ];
$this->assertArraySubset($expected, $result->toArray()); $this->assertArraySubset($expected, $result->toArray());

View File

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

View File

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

View File

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

View File

@ -1025,7 +1025,9 @@ schema {
query: Hello query: Hello
} }
type Hello implements Bar { } type Hello implements Bar {
field: String
}
'; ';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$schema = BuildSchema::buildAST($doc); $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 myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition
} }
extend type MyObj @onObject
scalar MyScalar @onScalar scalar MyScalar @onScalar
interface MyInterface @onInterface { interface MyInterface @onInterface {

View File

@ -3,6 +3,7 @@ namespace GraphQL\Tests\Validator;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType; 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([ $queryRoot = new ObjectType([
'name' => 'QueryRoot', 'name' => 'QueryRoot',
'fields' => [ 'fields' => [
@ -274,6 +293,14 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
'dogOrHuman' => ['type' => $DogOrHuman], 'dogOrHuman' => ['type' => $DogOrHuman],
'humanOrAlien' => ['type' => $HumanOrAlien], 'humanOrAlien' => ['type' => $HumanOrAlien],
'complicatedArgs' => ['type' => $ComplicatedArgs], '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\GraphQL::class,
\GraphQL\Type\Definition\Type::class, \GraphQL\Type\Definition\Type::class,
\GraphQL\Type\Definition\ResolveInfo::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\SchemaConfig::class,
\GraphQL\Type\Schema::class, \GraphQL\Type\Schema::class,
\GraphQL\Language\Parser::class, \GraphQL\Language\Parser::class,