mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-06 07:49:24 +03:00
Updated parser to 2016 spec version; schema language parsing
This commit is contained in:
parent
86adfde0a0
commit
4f4776726d
@ -4,6 +4,13 @@ namespace GraphQL\Language;
|
|||||||
// language/parser.js
|
// language/parser.js
|
||||||
|
|
||||||
use GraphQL\Language\AST\Argument;
|
use GraphQL\Language\AST\Argument;
|
||||||
|
use GraphQL\Language\AST\DirectiveDefinition;
|
||||||
|
use GraphQL\Language\AST\EnumTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\EnumValueDefinition;
|
||||||
|
use GraphQL\Language\AST\FieldDefinition;
|
||||||
|
use GraphQL\Language\AST\InputObjectTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\InputValueDefinition;
|
||||||
|
use GraphQL\Language\AST\InterfaceTypeDefinition;
|
||||||
use GraphQL\Language\AST\ListValue;
|
use GraphQL\Language\AST\ListValue;
|
||||||
use GraphQL\Language\AST\BooleanValue;
|
use GraphQL\Language\AST\BooleanValue;
|
||||||
use GraphQL\Language\AST\Directive;
|
use GraphQL\Language\AST\Directive;
|
||||||
@ -21,10 +28,16 @@ use GraphQL\Language\AST\Name;
|
|||||||
use GraphQL\Language\AST\NamedType;
|
use GraphQL\Language\AST\NamedType;
|
||||||
use GraphQL\Language\AST\NonNullType;
|
use GraphQL\Language\AST\NonNullType;
|
||||||
use GraphQL\Language\AST\ObjectField;
|
use GraphQL\Language\AST\ObjectField;
|
||||||
|
use GraphQL\Language\AST\ObjectTypeDefinition;
|
||||||
use GraphQL\Language\AST\ObjectValue;
|
use GraphQL\Language\AST\ObjectValue;
|
||||||
use GraphQL\Language\AST\OperationDefinition;
|
use GraphQL\Language\AST\OperationDefinition;
|
||||||
|
use GraphQL\Language\AST\ScalarTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\SchemaDefinition;
|
||||||
use GraphQL\Language\AST\SelectionSet;
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
use GraphQL\Language\AST\StringValue;
|
use GraphQL\Language\AST\StringValue;
|
||||||
|
use GraphQL\Language\AST\TypeExtensionDefinition;
|
||||||
|
use GraphQL\Language\AST\TypeSystemDefinition;
|
||||||
|
use GraphQL\Language\AST\UnionTypeDefinition;
|
||||||
use GraphQL\Language\AST\Variable;
|
use GraphQL\Language\AST\Variable;
|
||||||
use GraphQL\Language\AST\VariableDefinition;
|
use GraphQL\Language\AST\VariableDefinition;
|
||||||
use GraphQL\SyntaxError;
|
use GraphQL\SyntaxError;
|
||||||
@ -48,7 +61,7 @@ class Parser
|
|||||||
* @param array $options
|
* @param array $options
|
||||||
* @return Document
|
* @return Document
|
||||||
*/
|
*/
|
||||||
public static function parse($source, array $options = array())
|
public static function parse($source, array $options = [])
|
||||||
{
|
{
|
||||||
$sourceObj = $source instanceof Source ? $source : new Source($source);
|
$sourceObj = $source instanceof Source ? $source : new Source($source);
|
||||||
$parser = new self($sourceObj, $options);
|
$parser = new self($sourceObj, $options);
|
||||||
@ -80,7 +93,7 @@ class Parser
|
|||||||
*/
|
*/
|
||||||
private $token;
|
private $token;
|
||||||
|
|
||||||
function __construct(Source $source, array $options = array())
|
function __construct(Source $source, array $options = [])
|
||||||
{
|
{
|
||||||
$this->lexer = new Lexer($source);
|
$this->lexer = new Lexer($source);
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
@ -212,13 +225,13 @@ class Parser
|
|||||||
* @param callable $parseFn
|
* @param callable $parseFn
|
||||||
* @param int $closeKind
|
* @param int $closeKind
|
||||||
* @return array
|
* @return array
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function any($openKind, $parseFn, $closeKind)
|
function any($openKind, $parseFn, $closeKind)
|
||||||
{
|
{
|
||||||
$this->expect($openKind);
|
$this->expect($openKind);
|
||||||
|
|
||||||
$nodes = array();
|
$nodes = [];
|
||||||
while (!$this->skip($closeKind)) {
|
while (!$this->skip($closeKind)) {
|
||||||
$nodes[] = $parseFn($this);
|
$nodes[] = $parseFn($this);
|
||||||
}
|
}
|
||||||
@ -235,13 +248,13 @@ class Parser
|
|||||||
* @param $parseFn
|
* @param $parseFn
|
||||||
* @param $closeKind
|
* @param $closeKind
|
||||||
* @return array
|
* @return array
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function many($openKind, $parseFn, $closeKind)
|
function many($openKind, $parseFn, $closeKind)
|
||||||
{
|
{
|
||||||
$this->expect($openKind);
|
$this->expect($openKind);
|
||||||
|
|
||||||
$nodes = array($parseFn($this));
|
$nodes = [$parseFn($this)];
|
||||||
while (!$this->skip($closeKind)) {
|
while (!$this->skip($closeKind)) {
|
||||||
$nodes[] = $parseFn($this);
|
$nodes[] = $parseFn($this);
|
||||||
}
|
}
|
||||||
@ -252,16 +265,16 @@ class Parser
|
|||||||
* Converts a name lex token into a name parse node.
|
* Converts a name lex token into a name parse node.
|
||||||
*
|
*
|
||||||
* @return Name
|
* @return Name
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseName()
|
function parseName()
|
||||||
{
|
{
|
||||||
$token = $this->expect(Token::NAME);
|
$token = $this->expect(Token::NAME);
|
||||||
|
|
||||||
return new Name(array(
|
return new Name([
|
||||||
'value' => $token->value,
|
'value' => $token->value,
|
||||||
'loc' => $this->loc($token->start)
|
'loc' => $this->loc($token->start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,66 +293,113 @@ class Parser
|
|||||||
* Implements the parsing rules in the Document section.
|
* Implements the parsing rules in the Document section.
|
||||||
*
|
*
|
||||||
* @return Document
|
* @return Document
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseDocument()
|
function parseDocument()
|
||||||
{
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$definitions = array();
|
$definitions = [];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if ($this->peek(Token::BRACE_L)) {
|
$definitions[] = $this->parseDefinition();
|
||||||
$definitions[] = $this->parseOperationDefinition();
|
|
||||||
} else if ($this->peek(Token::NAME)) {
|
|
||||||
if ($this->token->value === 'query' || $this->token->value === 'mutation') {
|
|
||||||
$definitions[] = $this->parseOperationDefinition();
|
|
||||||
} else if ($this->token->value === 'fragment') {
|
|
||||||
$definitions[] = $this->parseFragmentDefinition();
|
|
||||||
} else {
|
|
||||||
throw $this->unexpected();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw $this->unexpected();
|
|
||||||
}
|
|
||||||
} while (!$this->skip(Token::EOF));
|
} while (!$this->skip(Token::EOF));
|
||||||
|
|
||||||
return new Document(array(
|
return new Document([
|
||||||
'definitions' => $definitions,
|
'definitions' => $definitions,
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return OperationDefinition|FragmentDefinition|TypeSystemDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseDefinition()
|
||||||
|
{
|
||||||
|
if ($this->peek(Token::BRACE_L)) {
|
||||||
|
return $this->parseOperationDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->peek(Token::NAME)) {
|
||||||
|
switch ($this->token->value) {
|
||||||
|
case 'query':
|
||||||
|
case 'mutation':
|
||||||
|
// Note: subscription is an experimental non-spec addition.
|
||||||
|
case 'subscription':
|
||||||
|
return $this->parseOperationDefinition();
|
||||||
|
|
||||||
|
case 'fragment':
|
||||||
|
return $this->parseFragmentDefinition();
|
||||||
|
|
||||||
|
// Note: the Type System IDL is an experimental non-spec addition.
|
||||||
|
case 'schema':
|
||||||
|
case 'scalar':
|
||||||
|
case 'type':
|
||||||
|
case 'interface':
|
||||||
|
case 'union':
|
||||||
|
case 'enum':
|
||||||
|
case 'input':
|
||||||
|
case 'extend':
|
||||||
|
case 'directive':
|
||||||
|
return $this->parseTypeSystemDefinition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $this->unexpected();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the parsing rules in the Operations section.
|
// Implements the parsing rules in the Operations section.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return OperationDefinition
|
* @return OperationDefinition
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseOperationDefinition()
|
function parseOperationDefinition()
|
||||||
{
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
if ($this->peek(Token::BRACE_L)) {
|
if ($this->peek(Token::BRACE_L)) {
|
||||||
return new OperationDefinition(array(
|
return new OperationDefinition([
|
||||||
'operation' => 'query',
|
'operation' => 'query',
|
||||||
'name' => null,
|
'name' => null,
|
||||||
'variableDefinitions' => null,
|
'variableDefinitions' => null,
|
||||||
'directives' => array(),
|
'directives' => [],
|
||||||
'selectionSet' => $this->parseSelectionSet(),
|
'selectionSet' => $this->parseSelectionSet(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$operationToken = $this->expect(Token::NAME);
|
$operation = $this->parseOperationType();
|
||||||
$operation = $operationToken->value;
|
|
||||||
|
|
||||||
return new OperationDefinition(array(
|
$name = null;
|
||||||
|
if ($this->peek(Token::NAME)) {
|
||||||
|
$name = $this->parseName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OperationDefinition([
|
||||||
'operation' => $operation,
|
'operation' => $operation,
|
||||||
'name' => $this->parseName(),
|
'name' => $name,
|
||||||
'variableDefinitions' => $this->parseVariableDefinitions(),
|
'variableDefinitions' => $this->parseVariableDefinitions(),
|
||||||
'directives' => $this->parseDirectives(),
|
'directives' => $this->parseDirectives(),
|
||||||
'selectionSet' => $this->parseSelectionSet(),
|
'selectionSet' => $this->parseSelectionSet(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseOperationType()
|
||||||
|
{
|
||||||
|
$operationToken = $this->expect(Token::NAME);
|
||||||
|
switch ($operationToken->value) {
|
||||||
|
case 'query': return 'query';
|
||||||
|
case 'mutation': return 'mutation';
|
||||||
|
// Note: subscription is an experimental non-spec addition.
|
||||||
|
case 'subscription': return 'subscription';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $this->unexpected($operationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -350,15 +410,15 @@ class Parser
|
|||||||
return $this->peek(Token::PAREN_L) ?
|
return $this->peek(Token::PAREN_L) ?
|
||||||
$this->many(
|
$this->many(
|
||||||
Token::PAREN_L,
|
Token::PAREN_L,
|
||||||
array($this, 'parseVariableDefinition'),
|
[$this, 'parseVariableDefinition'],
|
||||||
Token::PAREN_R
|
Token::PAREN_R
|
||||||
) :
|
) :
|
||||||
array();
|
[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return VariableDefinition
|
* @return VariableDefinition
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseVariableDefinition()
|
function parseVariableDefinition()
|
||||||
{
|
{
|
||||||
@ -368,44 +428,47 @@ class Parser
|
|||||||
$this->expect(Token::COLON);
|
$this->expect(Token::COLON);
|
||||||
$type = $this->parseType();
|
$type = $this->parseType();
|
||||||
|
|
||||||
return new VariableDefinition(array(
|
return new VariableDefinition([
|
||||||
'variable' => $var,
|
'variable' => $var,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'defaultValue' =>
|
'defaultValue' =>
|
||||||
($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null),
|
($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Variable
|
* @return Variable
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseVariable() {
|
function parseVariable()
|
||||||
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$this->expect(Token::DOLLAR);
|
$this->expect(Token::DOLLAR);
|
||||||
|
|
||||||
return new Variable(array(
|
return new Variable([
|
||||||
'name' => $this->parseName(),
|
'name' => $this->parseName(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return SelectionSet
|
* @return SelectionSet
|
||||||
*/
|
*/
|
||||||
function parseSelectionSet() {
|
function parseSelectionSet()
|
||||||
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
return new SelectionSet(array(
|
return new SelectionSet([
|
||||||
'selections' => $this->many(Token::BRACE_L, array($this, 'parseSelection'), Token::BRACE_R),
|
'selections' => $this->many(Token::BRACE_L, [$this, 'parseSelection'], Token::BRACE_R),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
function parseSelection() {
|
function parseSelection()
|
||||||
|
{
|
||||||
return $this->peek(Token::SPREAD) ?
|
return $this->peek(Token::SPREAD) ?
|
||||||
$this->parseFragment() :
|
$this->parseFragment() :
|
||||||
$this->parseField();
|
$this->parseField();
|
||||||
@ -414,7 +477,8 @@ class Parser
|
|||||||
/**
|
/**
|
||||||
* @return Field
|
* @return Field
|
||||||
*/
|
*/
|
||||||
function parseField() {
|
function parseField()
|
||||||
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$nameOrAlias = $this->parseName();
|
$nameOrAlias = $this->parseName();
|
||||||
|
|
||||||
@ -426,28 +490,29 @@ class Parser
|
|||||||
$name = $nameOrAlias;
|
$name = $nameOrAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Field(array(
|
return new Field([
|
||||||
'alias' => $alias,
|
'alias' => $alias,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'arguments' => $this->parseArguments(),
|
'arguments' => $this->parseArguments(),
|
||||||
'directives' => $this->parseDirectives(),
|
'directives' => $this->parseDirectives(),
|
||||||
'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)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Argument>
|
* @return array<Argument>
|
||||||
*/
|
*/
|
||||||
function parseArguments() {
|
function parseArguments()
|
||||||
|
{
|
||||||
return $this->peek(Token::PAREN_L) ?
|
return $this->peek(Token::PAREN_L) ?
|
||||||
$this->many(Token::PAREN_L, array($this, 'parseArgument'), Token::PAREN_R) :
|
$this->many(Token::PAREN_L, [$this, 'parseArgument'], Token::PAREN_R) :
|
||||||
array();
|
[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Argument
|
* @return Argument
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseArgument()
|
function parseArgument()
|
||||||
{
|
{
|
||||||
@ -457,44 +522,52 @@ class Parser
|
|||||||
$this->expect(Token::COLON);
|
$this->expect(Token::COLON);
|
||||||
$value = $this->parseValueLiteral(false);
|
$value = $this->parseValueLiteral(false);
|
||||||
|
|
||||||
return new Argument(array(
|
return new Argument([
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'value' => $value,
|
'value' => $value,
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the parsing rules in the Fragments section.
|
// Implements the parsing rules in the Fragments section.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FragmentSpread|InlineFragment
|
* @return FragmentSpread|InlineFragment
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseFragment() {
|
function parseFragment()
|
||||||
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$this->expect(Token::SPREAD);
|
$this->expect(Token::SPREAD);
|
||||||
|
|
||||||
if ($this->token->value === 'on') {
|
if ($this->peek(Token::NAME) && $this->token->value !== 'on') {
|
||||||
$this->advance();
|
return new FragmentSpread([
|
||||||
return new InlineFragment(array(
|
|
||||||
'typeCondition' => $this->parseNamedType(),
|
|
||||||
'directives' => $this->parseDirectives(),
|
|
||||||
'selectionSet' => $this->parseSelectionSet(),
|
|
||||||
'loc' => $this->loc($start)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return new FragmentSpread(array(
|
|
||||||
'name' => $this->parseFragmentName(),
|
'name' => $this->parseFragmentName(),
|
||||||
'directives' => $this->parseDirectives(),
|
'directives' => $this->parseDirectives(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeCondition = null;
|
||||||
|
if ($this->token->value === 'on') {
|
||||||
|
$this->advance();
|
||||||
|
$typeCondition = $this->parseNamedType();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InlineFragment([
|
||||||
|
'typeCondition' => $typeCondition,
|
||||||
|
'directives' => $this->parseDirectives(),
|
||||||
|
'selectionSet' => $this->parseSelectionSet(),
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FragmentDefinition
|
* @return FragmentDefinition
|
||||||
* @throws SyntaxError
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseFragmentDefinition() {
|
function parseFragmentDefinition()
|
||||||
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$this->expectKeyword('fragment');
|
$this->expectKeyword('fragment');
|
||||||
|
|
||||||
@ -502,13 +575,13 @@ class Parser
|
|||||||
$this->expectKeyword('on');
|
$this->expectKeyword('on');
|
||||||
$typeCondition = $this->parseNamedType();
|
$typeCondition = $this->parseNamedType();
|
||||||
|
|
||||||
return new FragmentDefinition(array(
|
return new FragmentDefinition([
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'typeCondition' => $typeCondition,
|
'typeCondition' => $typeCondition,
|
||||||
'directives' => $this->parseDirectives(),
|
'directives' => $this->parseDirectives(),
|
||||||
'selectionSet' => $this->parseSelectionSet(),
|
'selectionSet' => $this->parseSelectionSet(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the parsing rules in the Values section.
|
// Implements the parsing rules in the Values section.
|
||||||
@ -519,7 +592,7 @@ class Parser
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseConstValue()
|
function parseConstValue()
|
||||||
{
|
{
|
||||||
@ -531,7 +604,8 @@ class Parser
|
|||||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
||||||
* @throws SyntaxError
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseValueLiteral($isConst) {
|
function parseValueLiteral($isConst)
|
||||||
|
{
|
||||||
$token = $this->token;
|
$token = $this->token;
|
||||||
switch ($token->kind) {
|
switch ($token->kind) {
|
||||||
case Token::BRACKET_L:
|
case Token::BRACKET_L:
|
||||||
@ -540,35 +614,35 @@ class Parser
|
|||||||
return $this->parseObject($isConst);
|
return $this->parseObject($isConst);
|
||||||
case Token::INT:
|
case Token::INT:
|
||||||
$this->advance();
|
$this->advance();
|
||||||
return new IntValue(array(
|
return new IntValue([
|
||||||
'value' => $token->value,
|
'value' => $token->value,
|
||||||
'loc' => $this->loc($token->start)
|
'loc' => $this->loc($token->start)
|
||||||
));
|
]);
|
||||||
case Token::FLOAT:
|
case Token::FLOAT:
|
||||||
$this->advance();
|
$this->advance();
|
||||||
return new FloatValue(array(
|
return new FloatValue([
|
||||||
'value' => $token->value,
|
'value' => $token->value,
|
||||||
'loc' => $this->loc($token->start)
|
'loc' => $this->loc($token->start)
|
||||||
));
|
]);
|
||||||
case Token::STRING:
|
case Token::STRING:
|
||||||
$this->advance();
|
$this->advance();
|
||||||
return new StringValue(array(
|
return new StringValue([
|
||||||
'value' => $token->value,
|
'value' => $token->value,
|
||||||
'loc' => $this->loc($token->start)
|
'loc' => $this->loc($token->start)
|
||||||
));
|
]);
|
||||||
case Token::NAME:
|
case Token::NAME:
|
||||||
if ($token->value === 'true' || $token->value === 'false') {
|
if ($token->value === 'true' || $token->value === 'false') {
|
||||||
$this->advance();
|
$this->advance();
|
||||||
return new BooleanValue(array(
|
return new BooleanValue([
|
||||||
'value' => $token->value === 'true',
|
'value' => $token->value === 'true',
|
||||||
'loc' => $this->loc($token->start)
|
'loc' => $this->loc($token->start)
|
||||||
));
|
]);
|
||||||
} else if ($token->value !== 'null') {
|
} else if ($token->value !== 'null') {
|
||||||
$this->advance();
|
$this->advance();
|
||||||
return new EnumValue(array(
|
return new EnumValue([
|
||||||
'value' => $token->value,
|
'value' => $token->value,
|
||||||
'loc' => $this->loc($token->start)
|
'loc' => $this->loc($token->start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -589,25 +663,25 @@ class Parser
|
|||||||
{
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$item = $isConst ? 'parseConstValue' : 'parseVariableValue';
|
$item = $isConst ? 'parseConstValue' : 'parseVariableValue';
|
||||||
return new ListValue(array(
|
return new ListValue([
|
||||||
'values' => $this->any(Token::BRACKET_L, array($this, $item), Token::BRACKET_R),
|
'values' => $this->any(Token::BRACKET_L, [$this, $item], Token::BRACKET_R),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseObject($isConst)
|
function parseObject($isConst)
|
||||||
{
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$this->expect(Token::BRACE_L);
|
$this->expect(Token::BRACE_L);
|
||||||
$fieldNames = array();
|
$fieldNames = [];
|
||||||
$fields = array();
|
$fields = [];
|
||||||
while (!$this->skip(Token::BRACE_R)) {
|
while (!$this->skip(Token::BRACE_R)) {
|
||||||
$fields[] = $this->parseObjectField($isConst, $fieldNames);
|
$fields[] = $this->parseObjectField($isConst, $fieldNames);
|
||||||
}
|
}
|
||||||
return new ObjectValue(array(
|
return new ObjectValue([
|
||||||
'fields' => $fields,
|
'fields' => $fields,
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseObjectField($isConst, &$fieldNames)
|
function parseObjectField($isConst, &$fieldNames)
|
||||||
@ -621,11 +695,11 @@ class Parser
|
|||||||
$fieldNames[$name->value] = true;
|
$fieldNames[$name->value] = true;
|
||||||
$this->expect(Token::COLON);
|
$this->expect(Token::COLON);
|
||||||
|
|
||||||
return new ObjectField(array(
|
return new ObjectField([
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'value' => $this->parseValueLiteral($isConst),
|
'value' => $this->parseValueLiteral($isConst),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the parsing rules in the Directives section.
|
// Implements the parsing rules in the Directives section.
|
||||||
@ -635,7 +709,7 @@ class Parser
|
|||||||
*/
|
*/
|
||||||
function parseDirectives()
|
function parseDirectives()
|
||||||
{
|
{
|
||||||
$directives = array();
|
$directives = [];
|
||||||
while ($this->peek(Token::AT)) {
|
while ($this->peek(Token::AT)) {
|
||||||
$directives[] = $this->parseDirective();
|
$directives[] = $this->parseDirective();
|
||||||
}
|
}
|
||||||
@ -644,17 +718,17 @@ class Parser
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Directive
|
* @return Directive
|
||||||
* @throws Exception
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function parseDirective()
|
function parseDirective()
|
||||||
{
|
{
|
||||||
$start = $this->token->start;
|
$start = $this->token->start;
|
||||||
$this->expect(Token::AT);
|
$this->expect(Token::AT);
|
||||||
return new Directive(array(
|
return new Directive([
|
||||||
'name' => $this->parseName(),
|
'name' => $this->parseName(),
|
||||||
'arguments' => $this->parseArguments(),
|
'arguments' => $this->parseArguments(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the parsing rules in the Types section.
|
// Implements the parsing rules in the Types section.
|
||||||
@ -672,18 +746,18 @@ class Parser
|
|||||||
if ($this->skip(Token::BRACKET_L)) {
|
if ($this->skip(Token::BRACKET_L)) {
|
||||||
$type = $this->parseType();
|
$type = $this->parseType();
|
||||||
$this->expect(Token::BRACKET_R);
|
$this->expect(Token::BRACKET_R);
|
||||||
$type = new ListType(array(
|
$type = new ListType([
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
} else {
|
} else {
|
||||||
$type = $this->parseNamedType();
|
$type = $this->parseNamedType();
|
||||||
}
|
}
|
||||||
if ($this->skip(Token::BANG)) {
|
if ($this->skip(Token::BANG)) {
|
||||||
return new NonNullType(array(
|
return new NonNullType([
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
));
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
return $type;
|
return $type;
|
||||||
@ -697,6 +771,333 @@ class Parser
|
|||||||
'name' => $this->parseName(),
|
'name' => $this->parseName(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the parsing rules in the Type Definition section.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TypeSystemDefinition :
|
||||||
|
* - TypeDefinition
|
||||||
|
* - TypeExtensionDefinition
|
||||||
|
* - DirectiveDefinition
|
||||||
|
*
|
||||||
|
* TypeDefinition :
|
||||||
|
* - ScalarTypeDefinition
|
||||||
|
* - ObjectTypeDefinition
|
||||||
|
* - InterfaceTypeDefinition
|
||||||
|
* - UnionTypeDefinition
|
||||||
|
* - EnumTypeDefinition
|
||||||
|
* - InputObjectTypeDefinition
|
||||||
|
*
|
||||||
|
* @return TypeSystemDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseTypeSystemDefinition()
|
||||||
|
{
|
||||||
|
if ($this->peek(Token::NAME)) {
|
||||||
|
switch ($this->token->value) {
|
||||||
|
case 'schema': return $this->parseSchemaDefinition();
|
||||||
|
case 'scalar': return $this->parseScalarTypeDefinition();
|
||||||
|
case 'type': return $this->parseObjectTypeDefinition();
|
||||||
|
case 'interface': return $this->parseInterfaceTypeDefinition();
|
||||||
|
case 'union': return $this->parseUnionTypeDefinition();
|
||||||
|
case 'enum': return $this->parseEnumTypeDefinition();
|
||||||
|
case 'input': return $this->parseInputObjectTypeDefinition();
|
||||||
|
case 'extend': return $this->parseTypeExtensionDefinition();
|
||||||
|
case 'directive': return $this->parseDirectiveDefinition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $this->unexpected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return SchemaDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseSchemaDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('schema');
|
||||||
|
|
||||||
|
$operationTypes = $this->many(
|
||||||
|
Token::BRACE_L,
|
||||||
|
[$this, 'parseOperationTypeDefinition'],
|
||||||
|
Token::BRACE_R
|
||||||
|
);
|
||||||
|
|
||||||
|
return new SchemaDefinition([
|
||||||
|
'operationTypes' => $operationTypes,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ScalarTypeDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseScalarTypeDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('scalar');
|
||||||
|
$name = $this->parseName();
|
||||||
|
|
||||||
|
return new ScalarTypeDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ObjectTypeDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseObjectTypeDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('type');
|
||||||
|
$name = $this->parseName();
|
||||||
|
$interfaces = $this->parseImplementsInterfaces();
|
||||||
|
$fields = $this->any(
|
||||||
|
Token::BRACE_L,
|
||||||
|
[$this, 'parseFieldDefinition'],
|
||||||
|
Token::BRACE_R
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ObjectTypeDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'interfaces' => $interfaces,
|
||||||
|
'fields' => $fields,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return NamedType[]
|
||||||
|
*/
|
||||||
|
function parseImplementsInterfaces()
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
if ($this->token->value === 'implements') {
|
||||||
|
$this->advance();
|
||||||
|
do {
|
||||||
|
$types[] = $this->parseNamedType();
|
||||||
|
} while (!$this->peek(Token::BRACE_L));
|
||||||
|
}
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FieldDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseFieldDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$name = $this->parseName();
|
||||||
|
$args = $this->parseArgumentDefs();
|
||||||
|
$this->expect(Token::COLON);
|
||||||
|
$type = $this->parseType();
|
||||||
|
|
||||||
|
return new FieldDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'arguments' => $args,
|
||||||
|
'type' => $type,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InputValueDefinition[]
|
||||||
|
*/
|
||||||
|
function parseArgumentDefs()
|
||||||
|
{
|
||||||
|
if (!$this->peek(Token::PAREN_L)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $this->many(Token::PAREN_L, [$this, 'parseInputValueDef'], Token::PAREN_R);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InputValueDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseInputValueDef()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$name = $this->parseName();
|
||||||
|
$this->expect(Token::COLON);
|
||||||
|
$type = $this->parseType();
|
||||||
|
$defaultValue = null;
|
||||||
|
if ($this->skip(Token::EQUALS)) {
|
||||||
|
$defaultValue = $this->parseConstValue();
|
||||||
|
}
|
||||||
|
return new InputValueDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $type,
|
||||||
|
'defaultValue' => $defaultValue,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InterfaceTypeDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseInterfaceTypeDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('interface');
|
||||||
|
$name = $this->parseName();
|
||||||
|
$fields = $this->any(
|
||||||
|
Token::BRACE_L,
|
||||||
|
[$this, 'parseFieldDefinition'],
|
||||||
|
Token::BRACE_R
|
||||||
|
);
|
||||||
|
|
||||||
|
return new InterfaceTypeDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'fields' => $fields,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UnionTypeDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseUnionTypeDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('union');
|
||||||
|
$name = $this->parseName();
|
||||||
|
$this->expect(Token::EQUALS);
|
||||||
|
$types = $this->parseUnionMembers();
|
||||||
|
|
||||||
|
return new UnionTypeDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'types' => $types,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return NamedType[]
|
||||||
|
*/
|
||||||
|
function parseUnionMembers()
|
||||||
|
{
|
||||||
|
$members = [];
|
||||||
|
do {
|
||||||
|
$members[] = $this->parseNamedType();
|
||||||
|
} while ($this->skip(Token::PIPE));
|
||||||
|
return $members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return EnumTypeDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseEnumTypeDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('enum');
|
||||||
|
$name = $this->parseName();
|
||||||
|
$values = $this->many(
|
||||||
|
Token::BRACE_L,
|
||||||
|
[$this, 'parseEnumValueDefinition'],
|
||||||
|
Token::BRACE_R
|
||||||
|
);
|
||||||
|
|
||||||
|
return new EnumTypeDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'values' => $values,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return EnumValueDefinition
|
||||||
|
*/
|
||||||
|
function parseEnumValueDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$name = $this->parseName();
|
||||||
|
|
||||||
|
return new EnumValueDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InputObjectTypeDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseInputObjectTypeDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('input');
|
||||||
|
$name = $this->parseName();
|
||||||
|
$fields = $this->any(
|
||||||
|
Token::BRACE_L,
|
||||||
|
[$this, 'parseInputValueDef'],
|
||||||
|
Token::BRACE_R
|
||||||
|
);
|
||||||
|
|
||||||
|
return new InputObjectTypeDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'fields' => $fields,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return TypeExtensionDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseTypeExtensionDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('extend');
|
||||||
|
$definition = $this->parseObjectTypeDefinition();
|
||||||
|
|
||||||
|
return new TypeExtensionDefinition([
|
||||||
|
'definition' => $definition,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DirectiveDefinition
|
||||||
|
* @throws SyntaxError
|
||||||
|
*/
|
||||||
|
function parseDirectiveDefinition()
|
||||||
|
{
|
||||||
|
$start = $this->token->start;
|
||||||
|
$this->expectKeyword('directive');
|
||||||
|
$this->expect(Token::AT);
|
||||||
|
$name = $this->parseName();
|
||||||
|
$args = $this->parseArgumentDefs();
|
||||||
|
$this->expectKeyword('on');
|
||||||
|
$locations = $this->parseDirectiveLocations();
|
||||||
|
|
||||||
|
return new DirectiveDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'arguments' => $args,
|
||||||
|
'locations' => $locations,
|
||||||
|
'loc' => $this->loc($start)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Name[]
|
||||||
|
*/
|
||||||
|
function parseDirectiveLocations()
|
||||||
|
{
|
||||||
|
$locations = [];
|
||||||
|
do {
|
||||||
|
$locations[] = $this->parseName();
|
||||||
|
} while ($this->skip(Token::PIPE));
|
||||||
|
return $locations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Tests\Language;
|
namespace GraphQL\Tests\Language;
|
||||||
|
|
||||||
use GraphQL\Error;
|
|
||||||
use GraphQL\Language\AST\Argument;
|
use GraphQL\Language\AST\Argument;
|
||||||
use GraphQL\Language\AST\Document;
|
use GraphQL\Language\AST\Document;
|
||||||
use GraphQL\Language\AST\Field;
|
use GraphQL\Language\AST\Field;
|
||||||
@ -10,16 +9,20 @@ use GraphQL\Language\AST\Location;
|
|||||||
use GraphQL\Language\AST\Name;
|
use GraphQL\Language\AST\Name;
|
||||||
use GraphQL\Language\AST\OperationDefinition;
|
use GraphQL\Language\AST\OperationDefinition;
|
||||||
use GraphQL\Language\AST\SelectionSet;
|
use GraphQL\Language\AST\SelectionSet;
|
||||||
|
use GraphQL\Language\AST\StringValue;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\SyntaxError;
|
use GraphQL\SyntaxError;
|
||||||
|
use GraphQL\Utils;
|
||||||
|
|
||||||
class ParserTest extends \PHPUnit_Framework_TestCase
|
class ParserTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @it accepts option to not include source
|
||||||
|
*/
|
||||||
public function testAcceptsOptionToNotIncludeSource()
|
public function testAcceptsOptionToNotIncludeSource()
|
||||||
{
|
{
|
||||||
// accepts option to not include source
|
|
||||||
$actual = Parser::parse('{ field }', ['noSource' => true]);
|
$actual = Parser::parse('{ field }', ['noSource' => true]);
|
||||||
|
|
||||||
$expected = new Document([
|
$expected = new Document([
|
||||||
@ -54,6 +57,9 @@ class ParserTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parse provides useful errors
|
||||||
|
*/
|
||||||
public function testParseProvidesUsefulErrors()
|
public function testParseProvidesUsefulErrors()
|
||||||
{
|
{
|
||||||
$run = function($num, $str, $expectedMessage, $expectedPositions = null, $expectedLocations = null) {
|
$run = function($num, $str, $expectedMessage, $expectedPositions = null, $expectedLocations = null) {
|
||||||
@ -72,6 +78,7 @@ class ParserTest extends \PHPUnit_Framework_TestCase
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$run(0, '{', "Syntax Error GraphQL (1:2) Expected Name, found EOF\n\n1: {\n ^\n", [1], [new SourceLocation(1,2)]);
|
||||||
$run(1,
|
$run(1,
|
||||||
'{ ...MissingOn }
|
'{ ...MissingOn }
|
||||||
fragment MissingOn Type
|
fragment MissingOn Type
|
||||||
@ -82,25 +89,33 @@ fragment MissingOn Type
|
|||||||
$run(2, '{ field: {} }', "Syntax Error GraphQL (1:10) Expected Name, found {\n\n1: { field: {} }\n ^\n");
|
$run(2, '{ field: {} }', "Syntax Error GraphQL (1:10) Expected Name, found {\n\n1: { field: {} }\n ^\n");
|
||||||
$run(3, 'notanoperation Foo { field }', "Syntax Error GraphQL (1:1) Unexpected Name \"notanoperation\"\n\n1: notanoperation Foo { field }\n ^\n");
|
$run(3, 'notanoperation Foo { field }', "Syntax Error GraphQL (1:1) Unexpected Name \"notanoperation\"\n\n1: notanoperation Foo { field }\n ^\n");
|
||||||
$run(4, '...', "Syntax Error GraphQL (1:1) Unexpected ...\n\n1: ...\n ^\n");
|
$run(4, '...', "Syntax Error GraphQL (1:1) Unexpected ...\n\n1: ...\n ^\n");
|
||||||
$run(5, '{', "Syntax Error GraphQL (1:2) Expected Name, found EOF\n\n1: {\n ^\n", [1], [new SourceLocation(1,2)]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parse provides useful error when using source
|
||||||
|
*/
|
||||||
public function testParseProvidesUsefulErrorWhenUsingSource()
|
public function testParseProvidesUsefulErrorWhenUsingSource()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Parser::parse(new Source('query', 'MyQuery.graphql'));
|
Parser::parse(new Source('query', 'MyQuery.graphql'));
|
||||||
$this->fail('Expected exception not thrown');
|
$this->fail('Expected exception not thrown');
|
||||||
} catch (SyntaxError $e) {
|
} catch (SyntaxError $e) {
|
||||||
$this->assertEquals("Syntax Error MyQuery.graphql (1:6) Expected Name, found EOF\n\n1: query\n ^\n", $e->getMessage());
|
$this->assertEquals("Syntax Error MyQuery.graphql (1:6) Expected {, found EOF\n\n1: query\n ^\n", $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses variable inline values
|
||||||
|
*/
|
||||||
public function testParsesVariableInlineValues()
|
public function testParsesVariableInlineValues()
|
||||||
{
|
{
|
||||||
// Following line should not throw:
|
// Following line should not throw:
|
||||||
Parser::parse('{ field(complex: { a: { b: [ $var ] } }) }');
|
Parser::parse('{ field(complex: { a: { b: [ $var ] } }) }');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses constant default values
|
||||||
|
*/
|
||||||
public function testParsesConstantDefaultValues()
|
public function testParsesConstantDefaultValues()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -114,39 +129,71 @@ fragment MissingOn Type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDuplicateKeysInInputObjectIsSyntaxError()
|
/**
|
||||||
{
|
* @it does not accept fragments spread of "on"
|
||||||
try {
|
*/
|
||||||
Parser::parse('{ field(arg: { a: 1, a: 2 }) }');
|
|
||||||
$this->fail('Expected exception not thrown');
|
|
||||||
} catch (SyntaxError $e) {
|
|
||||||
$this->assertEquals(
|
|
||||||
"Syntax Error GraphQL (1:22) Duplicate input object field a.\n\n1: { field(arg: { a: 1, a: 2 }) }\n ^\n",
|
|
||||||
$e->getMessage()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDoesNotAcceptFragmentsNamedOn()
|
public function testDoesNotAcceptFragmentsNamedOn()
|
||||||
{
|
{
|
||||||
// does not accept fragments named "on"
|
|
||||||
$this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:10) Unexpected Name "on"');
|
$this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:10) Unexpected Name "on"');
|
||||||
Parser::parse('fragment on on on { on }');
|
Parser::parse('fragment on on on { on }');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it does not accept fragments spread of "on"
|
||||||
|
*/
|
||||||
public function testDoesNotAcceptFragmentSpreadOfOn()
|
public function testDoesNotAcceptFragmentSpreadOfOn()
|
||||||
{
|
{
|
||||||
// does not accept fragments spread of "on"
|
|
||||||
$this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:9) Expected Name, found }');
|
$this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:9) Expected Name, found }');
|
||||||
Parser::parse('{ ...on }');
|
Parser::parse('{ ...on }');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it does not allow null as value
|
||||||
|
*/
|
||||||
public function testDoesNotAllowNullAsValue()
|
public function testDoesNotAllowNullAsValue()
|
||||||
{
|
{
|
||||||
$this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:39) Unexpected Name "null"');
|
$this->setExpectedException('GraphQL\SyntaxError', 'Syntax Error GraphQL (1:39) Unexpected Name "null"');
|
||||||
Parser::parse('{ fieldWithNullableStringInput(input: null) }');
|
Parser::parse('{ fieldWithNullableStringInput(input: null) }');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses multi-byte characters
|
||||||
|
*/
|
||||||
|
public function testParsesMultiByteCharacters()
|
||||||
|
{
|
||||||
|
// Note: \u0A0A could be naively interpretted as two line-feed chars.
|
||||||
|
|
||||||
|
$char = Utils::chr(0x0A0A);
|
||||||
|
$query = <<<HEREDOC
|
||||||
|
# This comment has a $char multi-byte character.
|
||||||
|
{ field(arg: "Has a $char multi-byte character.") }
|
||||||
|
HEREDOC;
|
||||||
|
|
||||||
|
$result = Parser::parse($query, ['noLocation' => true]);
|
||||||
|
|
||||||
|
$expected = new SelectionSet([
|
||||||
|
'selections' => [
|
||||||
|
new Field([
|
||||||
|
'name' => new Name(['value' => 'field']),
|
||||||
|
'arguments' => [
|
||||||
|
new Argument([
|
||||||
|
'name' => new Name(['value' => 'arg']),
|
||||||
|
'value' => new StringValue([
|
||||||
|
'value' => "Has a $char multi-byte character."
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'directives' => []
|
||||||
|
])
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses kitchen sink
|
||||||
|
*/
|
||||||
public function testParsesKitchenSink()
|
public function testParsesKitchenSink()
|
||||||
{
|
{
|
||||||
// Following should not throw:
|
// Following should not throw:
|
||||||
@ -155,14 +202,17 @@ fragment MissingOn Type
|
|||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows non-keywords anywhere a Name is allowed
|
||||||
|
*/
|
||||||
public function testAllowsNonKeywordsAnywhereANameIsAllowed()
|
public function testAllowsNonKeywordsAnywhereANameIsAllowed()
|
||||||
{
|
{
|
||||||
// allows non-keywords anywhere a Name is allowed
|
|
||||||
$nonKeywords = [
|
$nonKeywords = [
|
||||||
'on',
|
'on',
|
||||||
'fragment',
|
'fragment',
|
||||||
'query',
|
'query',
|
||||||
'mutation',
|
'mutation',
|
||||||
|
'subscription',
|
||||||
'true',
|
'true',
|
||||||
'false'
|
'false'
|
||||||
];
|
];
|
||||||
@ -185,6 +235,60 @@ fragment $fragmentName on Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses anonymous mutation operations
|
||||||
|
*/
|
||||||
|
public function testParsessAnonymousMutationOperations()
|
||||||
|
{
|
||||||
|
// Should not throw:
|
||||||
|
Parser::parse('
|
||||||
|
mutation {
|
||||||
|
mutationField
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses anonymous subscription operations
|
||||||
|
*/
|
||||||
|
public function testParsesAnonymousSubscriptionOperations()
|
||||||
|
{
|
||||||
|
// Should not throw:
|
||||||
|
Parser::parse('
|
||||||
|
subscription {
|
||||||
|
subscriptionField
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses named mutation operations
|
||||||
|
*/
|
||||||
|
public function testParsesNamedMutationOperations()
|
||||||
|
{
|
||||||
|
// Should not throw:
|
||||||
|
Parser::parse('
|
||||||
|
mutation Foo {
|
||||||
|
mutationField
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parses named subscription operations
|
||||||
|
*/
|
||||||
|
public function testParsesNamedSubscriptionOperations()
|
||||||
|
{
|
||||||
|
Parser::parse('
|
||||||
|
subscription Foo {
|
||||||
|
subscriptionField
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it parse creates ast
|
||||||
|
*/
|
||||||
public function testParseCreatesAst()
|
public function testParseCreatesAst()
|
||||||
{
|
{
|
||||||
$source = new Source('{
|
$source = new Source('{
|
||||||
|
598
tests/Language/SchemaParserTest.php
Normal file
598
tests/Language/SchemaParserTest.php
Normal file
@ -0,0 +1,598 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Language;
|
||||||
|
|
||||||
|
use GraphQL\Language\AST\BooleanValue;
|
||||||
|
use GraphQL\Language\AST\Document;
|
||||||
|
use GraphQL\Language\AST\EnumTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\EnumValueDefinition;
|
||||||
|
use GraphQL\Language\AST\FieldDefinition;
|
||||||
|
use GraphQL\Language\AST\InputObjectTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\InputValueDefinition;
|
||||||
|
use GraphQL\Language\AST\InterfaceTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\ListType;
|
||||||
|
use GraphQL\Language\AST\Location;
|
||||||
|
use GraphQL\Language\AST\Name;
|
||||||
|
use GraphQL\Language\AST\NamedType;
|
||||||
|
use GraphQL\Language\AST\NonNullType;
|
||||||
|
use GraphQL\Language\AST\ObjectTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\ScalarTypeDefinition;
|
||||||
|
use GraphQL\Language\AST\TypeExtensionDefinition;
|
||||||
|
use GraphQL\Language\AST\UnionTypeDefinition;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\Source;
|
||||||
|
|
||||||
|
class SchemaParserTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @it Simple type
|
||||||
|
*/
|
||||||
|
public function testSimpleType()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
world: String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNode(
|
||||||
|
$this->nameNode('world', $loc(16, 21)),
|
||||||
|
$this->typeNode('String', $loc(23, 29)),
|
||||||
|
$loc(16, 29)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 31)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 31)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($doc, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple extension
|
||||||
|
*/
|
||||||
|
public function testSimpleExtension()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
extend type Hello {
|
||||||
|
world: String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new TypeExtensionDefinition([
|
||||||
|
'definition' => new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(13, 18)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNode(
|
||||||
|
$this->nameNode('world', $loc(23, 28)),
|
||||||
|
$this->typeNode('String', $loc(30, 36)),
|
||||||
|
$loc(23, 36)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(8, 38)
|
||||||
|
]),
|
||||||
|
'loc' => $loc(1, 38)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 38)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple non-null type
|
||||||
|
*/
|
||||||
|
public function testSimpleNonNullType()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
world: String!
|
||||||
|
}';
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6,11)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNode(
|
||||||
|
$this->nameNode('world', $loc(16, 21)),
|
||||||
|
new NonNullType([
|
||||||
|
'type' => $this->typeNode('String', $loc(23, 29)),
|
||||||
|
'loc' => $loc(23, 30)
|
||||||
|
]),
|
||||||
|
$loc(16,30)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1,32)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1,32)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple type inheriting interface
|
||||||
|
*/
|
||||||
|
public function testSimpleTypeInheritingInterface()
|
||||||
|
{
|
||||||
|
$body = 'type Hello implements World { }';
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||||
|
'interfaces' => [
|
||||||
|
$this->typeNode('World', $loc(22, 27))
|
||||||
|
],
|
||||||
|
'fields' => [],
|
||||||
|
'loc' => $loc(0,31)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0,31)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple type inheriting multiple interfaces
|
||||||
|
*/
|
||||||
|
public function testSimpleTypeInheritingMultipleInterfaces()
|
||||||
|
{
|
||||||
|
$body = 'type Hello implements Wo, rld { }';
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||||
|
'interfaces' => [
|
||||||
|
$this->typeNode('Wo', $loc(22,24)),
|
||||||
|
$this->typeNode('rld', $loc(26,29))
|
||||||
|
],
|
||||||
|
'fields' => [],
|
||||||
|
'loc' => $loc(0, 33)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 33)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Single value enum
|
||||||
|
*/
|
||||||
|
public function testSingleValueEnum()
|
||||||
|
{
|
||||||
|
$body = 'enum Hello { WORLD }';
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new EnumTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||||
|
'values' => [$this->enumValueNode('WORLD', $loc(13, 18))],
|
||||||
|
'loc' => $loc(0, 20)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 20)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Double value enum
|
||||||
|
*/
|
||||||
|
public function testDoubleValueEnum()
|
||||||
|
{
|
||||||
|
$body = 'enum Hello { WO, RLD }';
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new EnumTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||||
|
'values' => [
|
||||||
|
$this->enumValueNode('WO', $loc(13, 15)),
|
||||||
|
$this->enumValueNode('RLD', $loc(17, 20))
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 22)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 22)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple interface
|
||||||
|
*/
|
||||||
|
public function testSimpleInterface()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
interface Hello {
|
||||||
|
world: String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new InterfaceTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(11, 16)),
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNode(
|
||||||
|
$this->nameNode('world', $loc(21, 26)),
|
||||||
|
$this->typeNode('String', $loc(28, 34)),
|
||||||
|
$loc(21, 34)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 36)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1,36)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple field with arg
|
||||||
|
*/
|
||||||
|
public function testSimpleFieldWithArg()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
world(flag: Boolean): String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNodeWithArgs(
|
||||||
|
$this->nameNode('world', $loc(16, 21)),
|
||||||
|
$this->typeNode('String', $loc(38, 44)),
|
||||||
|
[
|
||||||
|
$this->inputValueNode(
|
||||||
|
$this->nameNode('flag', $loc(22, 26)),
|
||||||
|
$this->typeNode('Boolean', $loc(28, 35)),
|
||||||
|
null,
|
||||||
|
$loc(22, 35)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
$loc(16, 44)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 46)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 46)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple field with arg with default value
|
||||||
|
*/
|
||||||
|
public function testSimpleFieldWithArgWithDefaultValue()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
world(flag: Boolean = true): String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNodeWithArgs(
|
||||||
|
$this->nameNode('world', $loc(16, 21)),
|
||||||
|
$this->typeNode('String', $loc(45, 51)),
|
||||||
|
[
|
||||||
|
$this->inputValueNode(
|
||||||
|
$this->nameNode('flag', $loc(22, 26)),
|
||||||
|
$this->typeNode('Boolean', $loc(28, 35)),
|
||||||
|
new BooleanValue(['value' => true, 'loc' => $loc(38, 42)]),
|
||||||
|
$loc(22, 42)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
$loc(16, 51)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 53)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 53)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple field with list arg
|
||||||
|
*/
|
||||||
|
public function testSimpleFieldWithListArg()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
world(things: [String]): String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNodeWithArgs(
|
||||||
|
$this->nameNode('world', $loc(16, 21)),
|
||||||
|
$this->typeNode('String', $loc(41, 47)),
|
||||||
|
[
|
||||||
|
$this->inputValueNode(
|
||||||
|
$this->nameNode('things', $loc(22,28)),
|
||||||
|
new ListType(['type' => $this->typeNode('String', $loc(31, 37)), 'loc' => $loc(30, 38)]),
|
||||||
|
null,
|
||||||
|
$loc(22, 38)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
$loc(16, 47)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 49)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 49)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple field with two args
|
||||||
|
*/
|
||||||
|
public function testSimpleFieldWithTwoArgs()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
type Hello {
|
||||||
|
world(argOne: Boolean, argTwo: Int): String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'interfaces' => [],
|
||||||
|
'fields' => [
|
||||||
|
$this->fieldNodeWithArgs(
|
||||||
|
$this->nameNode('world', $loc(16, 21)),
|
||||||
|
$this->typeNode('String', $loc(53, 59)),
|
||||||
|
[
|
||||||
|
$this->inputValueNode(
|
||||||
|
$this->nameNode('argOne', $loc(22, 28)),
|
||||||
|
$this->typeNode('Boolean', $loc(30, 37)),
|
||||||
|
null,
|
||||||
|
$loc(22, 37)
|
||||||
|
),
|
||||||
|
$this->inputValueNode(
|
||||||
|
$this->nameNode('argTwo', $loc(39, 45)),
|
||||||
|
$this->typeNode('Int', $loc(47, 50)),
|
||||||
|
null,
|
||||||
|
$loc(39, 50)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
$loc(16, 59)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 61)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 61)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple union
|
||||||
|
*/
|
||||||
|
public function testSimpleUnion()
|
||||||
|
{
|
||||||
|
$body = 'union Hello = World';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new UnionTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'types' => [$this->typeNode('World', $loc(14, 19))],
|
||||||
|
'loc' => $loc(0, 19)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 19)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Union with two types
|
||||||
|
*/
|
||||||
|
public function testUnionWithTwoTypes()
|
||||||
|
{
|
||||||
|
$body = 'union Hello = Wo | Rld';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new UnionTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||||
|
'types' => [
|
||||||
|
$this->typeNode('Wo', $loc(14, 16)),
|
||||||
|
$this->typeNode('Rld', $loc(19, 22))
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 22)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 22)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Scalar
|
||||||
|
*/
|
||||||
|
public function testScalar()
|
||||||
|
{
|
||||||
|
$body = 'scalar Hello';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new ScalarTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(7, 12)),
|
||||||
|
'loc' => $loc(0, 12)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(0, 12)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple input object
|
||||||
|
*/
|
||||||
|
public function testSimpleInputObject()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
input Hello {
|
||||||
|
world: String
|
||||||
|
}';
|
||||||
|
$doc = Parser::parse($body);
|
||||||
|
$loc = $this->createLocFn($body);
|
||||||
|
|
||||||
|
$expected = new Document([
|
||||||
|
'definitions' => [
|
||||||
|
new InputObjectTypeDefinition([
|
||||||
|
'name' => $this->nameNode('Hello', $loc(7, 12)),
|
||||||
|
'fields' => [
|
||||||
|
$this->inputValueNode(
|
||||||
|
$this->nameNode('world', $loc(17, 22)),
|
||||||
|
$this->typeNode('String', $loc(24, 30)),
|
||||||
|
null,
|
||||||
|
$loc(17, 30)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 32)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
'loc' => $loc(1, 32)
|
||||||
|
]);
|
||||||
|
$this->assertEquals($expected, $doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Simple input object with args should fail
|
||||||
|
*/
|
||||||
|
public function testSimpleInputObjectWithArgsShouldFail()
|
||||||
|
{
|
||||||
|
$body = '
|
||||||
|
input Hello {
|
||||||
|
world(foo: Int): String
|
||||||
|
}';
|
||||||
|
$this->setExpectedException('GraphQL\SyntaxError');
|
||||||
|
Parser::parse($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function createLocFn($body)
|
||||||
|
{
|
||||||
|
return function($start, $end) use ($body) {
|
||||||
|
return new Location($start, $end, new Source($body));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function typeNode($name, $loc)
|
||||||
|
{
|
||||||
|
return new NamedType([
|
||||||
|
'name' => new Name(['value' => $name, 'loc' => $loc]),
|
||||||
|
'loc' => $loc
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function nameNode($name, $loc)
|
||||||
|
{
|
||||||
|
return new Name([
|
||||||
|
'value' => $name,
|
||||||
|
'loc' => $loc
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fieldNode($name, $type, $loc)
|
||||||
|
{
|
||||||
|
return $this->fieldNodeWithArgs($name, $type, [], $loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fieldNodeWithArgs($name, $type, $args, $loc)
|
||||||
|
{
|
||||||
|
return new FieldDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'arguments' => $args,
|
||||||
|
'type' => $type,
|
||||||
|
'loc' => $loc
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function enumValueNode($name, $loc)
|
||||||
|
{
|
||||||
|
return new EnumValueDefinition([
|
||||||
|
'name' => $this->nameNode($name, $loc),
|
||||||
|
'loc' => $loc
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function inputValueNode($name, $type, $defaultValue, $loc)
|
||||||
|
{
|
||||||
|
return new InputValueDefinition([
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $type,
|
||||||
|
'defaultValue' => $defaultValue,
|
||||||
|
'loc' => $loc
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,12 @@ query queryName($foo: ComplexType, $site: Site = MOBILE) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
... @skip(unless: $foo) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
... {
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +34,19 @@ mutation likeStory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
|
||||||
|
storyLikeSubscribe(input: $input) {
|
||||||
|
story {
|
||||||
|
likers {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
likeSentence {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fragment frag on Friend {
|
fragment frag on Friend {
|
||||||
foo(size: $size, bar: $b, obj: {key: "value"})
|
foo(size: $size, bar: $b, obj: {key: "value"})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user