Add support for directives applied on IDL & Schema

This commit is contained in:
Vladimir Razuvaev 2017-09-20 17:43:06 +07:00
parent 39f378ece7
commit 6050af4e67
18 changed files with 246 additions and 77 deletions

View File

@ -9,22 +9,6 @@ use GraphQL\Utils\Utils;
*/
class CustomScalarType extends ScalarType
{
/**
* @var array
*/
public $config;
/**
* CustomScalarType constructor.
* @param array $config
*/
function __construct(array $config)
{
$this->name = $config['name'];
$this->config = $config;
parent::__construct();
}
/**
* @param mixed $value
* @return mixed

View File

@ -1,6 +1,8 @@
<?php
namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\DirectiveDefinitionNode;
/**
* Class Directive
* @package GraphQL\Type\Definition
@ -157,6 +159,16 @@ class Directive
*/
public $args;
/**
* @var DirectiveDefinitionNode|null
*/
public $astNode;
/**
* @var array
*/
public $config;
/**
* Directive constructor.
* @param array $config
@ -166,5 +178,6 @@ class Directive
foreach ($config as $key => $value) {
$this->{$key} = $value;
}
$this->config = $config;
}
}

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Utils\MixedStore;
use GraphQL\Utils\Utils;
@ -12,6 +13,11 @@ use GraphQL\Utils\Utils;
*/
class EnumType extends Type implements InputType, OutputType, LeafType
{
/**
* @var EnumTypeDefinitionNode|null
*/
public $astNode;
/**
* @var EnumValueDefinition[]
*/
@ -27,11 +33,6 @@ class EnumType extends Type implements InputType, OutputType, LeafType
*/
private $nameLookup;
/**
* @var array
*/
public $config;
public function __construct($config)
{
if (!isset($config['name'])) {
@ -53,6 +54,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType
$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;
}
@ -91,7 +93,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType
/**
* @param $name
* @return mixed|null
* @return EnumValueDefinition|null
*/
public function getValue($name)
{

View File

@ -1,5 +1,6 @@
<?php
namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Utils\Utils;
/**
@ -28,6 +29,11 @@ class EnumValueDefinition
*/
public $description;
/**
* @var EnumValueDefinitionNode|null
*/
public $astNode;
/**
* @var array
*/
@ -39,6 +45,7 @@ class EnumValueDefinition
$this->value = isset($config['value']) ? $config['value'] : null;
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
$this->description = isset($config['description']) ? $config['description'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;
}

View File

@ -2,6 +2,8 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Utils\Utils;
@ -9,7 +11,6 @@ use GraphQL\Utils\Utils;
* Class FieldArgument
*
* @package GraphQL\Type\Definition
* @todo Rename to ArgumentNode as it is also applicable to directives, not only fields
*/
class FieldArgument
{
@ -28,6 +29,11 @@ class FieldArgument
*/
public $description;
/**
* @var InputValueDefinitionNode|null
*/
public $astNode;
/**
* @var array
*/
@ -80,6 +86,9 @@ class FieldArgument
case 'description':
$this->description = $value;
break;
case 'astNode':
$this->astNode = $value;
break;
}
}
$this->config = $def;

View File

@ -1,6 +1,8 @@
<?php
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Utils\Utils;
/**
@ -48,6 +50,11 @@ class FieldDefinition
*/
public $deprecationReason;
/**
* @var FieldDefinitionNode|null
*/
public $astNode;
/**
* Original field definition config
*
@ -185,6 +192,7 @@ class FieldDefinition
$this->description = isset($config['description']) ? $config['description'] : null;
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;

View File

@ -1,5 +1,6 @@
<?php
namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\InputValueDefinitionNode;
/**
* Class InputObjectField
@ -27,6 +28,11 @@ class InputObjectField
*/
public $type;
/**
* @var InputValueDefinitionNode|null
*/
public $astNode;
/**
* @var array
*/

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Utils\Utils;
/**
@ -16,9 +17,9 @@ class InputObjectType extends Type implements InputType
private $fields;
/**
* @var array
* @var InputObjectTypeDefinitionNode|null
*/
public $config;
public $astNode;
/**
* InputObjectType constructor.
@ -45,6 +46,7 @@ class InputObjectType extends Type implements InputType
$this->config = $config;
$this->name = $config['name'];
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->description = isset($config['description']) ? $config['description'] : null;
}

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Utils\Utils;
/**
@ -16,14 +17,9 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
private $fields;
/**
* @var mixed|null
* @var InterfaceTypeDefinitionNode|null
*/
public $description;
/**
* @var array
*/
public $config;
public $astNode;
/**
* InterfaceType constructor.
@ -49,6 +45,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;
}

View File

@ -2,6 +2,8 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
use GraphQL\Utils\Utils;
@ -63,11 +65,14 @@ class ObjectType extends Type implements OutputType, CompositeType
private $interfaceMap;
/**
* Keeping reference of config for late bindings and custom app-level metadata
*
* @var array
* @var ObjectTypeDefinitionNode|null
*/
public $config;
public $astNode;
/**
* @var TypeExtensionDefinitionNode[]
*/
public $extensionASTNodes;
/**
* @var callable
@ -106,6 +111,8 @@ class ObjectType extends Type implements OutputType, CompositeType
$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->resolveFieldFn = isset($config['resolveField']) ? $config['resolveField'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->extensionASTNodes = isset($config['extensionASTNodes']) ? $config['extensionASTNodes'] : [];
$this->config = $config;
}

View File

@ -1,6 +1,7 @@
<?php
namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Utils\Utils;
/**
@ -24,13 +25,15 @@ use GraphQL\Utils\Utils;
abstract class ScalarType extends Type implements OutputType, InputType, LeafType
{
/**
* ScalarType constructor.
* @var ScalarTypeDefinitionNode|null
*/
public function __construct()
public $astNode;
function __construct(array $config = [])
{
if (!isset($this->name)) {
$this->name = $this->tryInferName();
}
$this->name = isset($config['name']) ? $config['name'] : $this->tryInferName();
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;
Utils::assertValidName($this->name);
}

View File

@ -2,7 +2,7 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Utils\Utils;
use GraphQL\Language\AST\TypeDefinitionNode;
/**
* Registry of standard GraphQL types
@ -202,6 +202,16 @@ abstract class Type implements \JsonSerializable
*/
public $description;
/**
* @var TypeDefinitionNode|null
*/
public $astNode;
/**
* @var array
*/
public $config;
/**
* @return null|string
*/

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Utils\Utils;
/**
@ -10,6 +11,11 @@ use GraphQL\Utils\Utils;
*/
class UnionType extends Type implements AbstractType, OutputType, CompositeType
{
/**
* @var UnionTypeDefinitionNode
*/
public $astNode;
/**
* @var ObjectType[]
*/
@ -20,11 +26,6 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
*/
private $possibleTypeNames;
/**
* @var array
*/
public $config;
/**
* UnionType constructor.
* @param $config
@ -51,6 +52,7 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
*/
$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;
}

View File

@ -3,6 +3,7 @@ namespace GraphQL\Type;
use GraphQL\Error\InvariantViolation;
use GraphQL\GraphQL;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\AbstractType;
@ -357,6 +358,14 @@ class Schema
return null;
}
/**
* @return SchemaDefinitionNode
*/
public function getAstNode()
{
return $this->config->getAstNode();
}
/**
* @param $typeName
* @return Type

View File

@ -1,6 +1,7 @@
<?php
namespace GraphQL\Type;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
@ -52,6 +53,11 @@ class SchemaConfig
*/
public $typeLoader;
/**
* @var SchemaDefinitionNode
*/
public $astNode;
/**
* Converts an array of options to instance of SchemaConfig
* (or just returns empty config when array is not passed).
@ -132,11 +138,38 @@ class SchemaConfig
);
$config->setTypeLoader($options['typeLoader']);
}
if (isset($options['astNode'])) {
Utils::invariant(
$options['astNode'] instanceof SchemaDefinitionNode,
'Schema astNode must be an instance of SchemaDefinitionNode but got: %s',
Utils::printSafe($options['typeLoader'])
);
$config->setAstNode($options['astNode']);
}
}
return $config;
}
/**
* @return SchemaDefinitionNode
*/
public function getAstNode()
{
return $this->astNode;
}
/**
* @param SchemaDefinitionNode $astNode
* @return SchemaConfig
*/
public function setAstNode(SchemaDefinitionNode $astNode)
{
$this->astNode = $astNode;
return $this;
}
/**
* @api
* @param ObjectType $query

View File

@ -241,6 +241,7 @@ class BuildSchema
return $this->typeDefNamed($name);
},
'directives' => $directives,
'astNode' => $schemaDef,
'types' => function() {
$types = [];
foreach ($this->nodeMap as $name => $def) {
@ -264,6 +265,7 @@ class BuildSchema
return $node->value;
}),
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
'astNode' => $directiveNode
]);
}
@ -428,7 +430,8 @@ class BuildSchema
},
'interfaces' => function() use ($def) {
return $this->makeImplementedInterfaces($def);
}
},
'astNode' => $def
];
}
@ -444,7 +447,8 @@ class BuildSchema
'type' => $this->produceOutputType($field->type),
'description' => $this->getDescription($field),
'args' => $this->makeInputValues($field->arguments),
'deprecationReason' => $this->getDeprecationReason($field)
'deprecationReason' => $this->getDeprecationReason($field),
'astNode' => $field
];
}
);
@ -472,7 +476,8 @@ class BuildSchema
$config = [
'name' => $value->name->value,
'type' => $type,
'description' => $this->getDescription($value)
'description' => $this->getDescription($value),
'astNode' => $value
];
if (isset($value->defaultValue)) {
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
@ -491,6 +496,7 @@ class BuildSchema
'fields' => function() use ($def) {
return $this->makeFieldDefMap($def);
},
'astNode' => $def,
'resolveType' => function() {
$this->cannotExecuteSchema();
}
@ -502,6 +508,7 @@ class BuildSchema
return [
'name' => $def->name->value,
'description' => $this->getDescription($def),
'astNode' => $def,
'values' => Utils::keyValMap(
$def->values,
function($enumValue) {
@ -510,7 +517,8 @@ class BuildSchema
function($enumValue) {
return [
'description' => $this->getDescription($enumValue),
'deprecationReason' => $this->getDeprecationReason($enumValue)
'deprecationReason' => $this->getDeprecationReason($enumValue),
'astNode' => $enumValue
];
}
)
@ -525,6 +533,7 @@ class BuildSchema
'types' => Utils::map($def->types, function($typeNode) {
return $this->produceObjectType($typeNode);
}),
'astNode' => $def,
'resolveType' => [$this, 'cannotExecuteSchema']
];
}
@ -534,6 +543,7 @@ class BuildSchema
return [
'name' => $def->name->value,
'description' => $this->getDescription($def),
'astNode' => $def,
'serialize' => function() {
return false;
},
@ -555,7 +565,8 @@ class BuildSchema
return [
'name' => $def->name->value,
'description' => $this->getDescription($def),
'fields' => function() use ($def) { return $this->makeInputValues($def->fields); }
'fields' => function() use ($def) { return $this->makeInputValues($def->fields); },
'astNode' => $def,
];
}

View File

@ -247,13 +247,14 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
'description' => null,
'deprecationReason' => 'Just because',
'value' => 'foo',
'astNode' => null
], (array) $value);
$this->assertEquals(true, $value->isDeprecated());
}
/**
* @it defines an enum type with a value of `null`
* @it defines an enum type with a value of `null` and `undefined`
*/
public function testDefinesAnEnumTypeWithAValueOfNullAndUndefined()
{
@ -271,12 +272,14 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
'description' => null,
'deprecationReason' => null,
'value' => null,
'astNode' => null,
],
[
'name' => 'UNDEFINED',
'description' => null,
'deprecationReason' => null,
'value' => null,
'astNode' => null,
],
];

View File

@ -8,6 +8,9 @@ use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\TypeNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\SchemaPrinter;
@ -588,26 +591,19 @@ type Query {
$ast = Parser::parse($body);
$schema = BuildSchema::buildAST($ast);
$this->assertEquals($schema->getType('MyEnum')->getValues(), [
new EnumValueDefinition([
'name' => 'VALUE',
'description' => '',
'deprecationReason' => null,
'value' => 'VALUE'
]),
new EnumValueDefinition([
'name' => 'OLD_VALUE',
'description' => '',
'deprecationReason' => 'No longer supported',
'value' => 'OLD_VALUE'
]),
new EnumValueDefinition([
'name' => 'OTHER_VALUE',
'description' => '',
'deprecationReason' => 'Terrible reasons',
'value' => 'OTHER_VALUE'
])
]);
/** @var EnumType $myEnum */
$myEnum = $schema->getType('MyEnum');
$value = $myEnum->getValue('VALUE');
$this->assertFalse($value->isDeprecated());
$oldValue = $myEnum->getValue('OLD_VALUE');
$this->assertTrue($oldValue->isDeprecated());
$this->assertEquals('No longer supported', $oldValue->deprecationReason);
$otherValue = $myEnum->getValue('OTHER_VALUE');
$this->assertTrue($otherValue->isDeprecated());
$this->assertEquals('Terrible reasons', $otherValue->deprecationReason);
$rootFields = $schema->getType('Query')->getFields();
$this->assertEquals($rootFields['field1']->isDeprecated(), true);
@ -617,6 +613,73 @@ type Query {
$this->assertEquals($rootFields['field2']->deprecationReason, 'Because I said so');
}
/**
* @it Correctly assign AST nodes
*/
public function testCorrectlyAssignASTNodes()
{
$schema = BuildSchema::build('
schema {
query: Query
}
type Query {
testField(testArg: TestInput): TestUnion
}
input TestInput {
testInputField: TestEnum
}
enum TestEnum {
TEST_VALUE
}
union TestUnion = TestType
interface TestInterface {
interfaceField: String
}
type TestType implements TestInterface {
interfaceField: String
}
directive @test(arg: Int) on FIELD
');
/** @var ObjectType $query */
$query = $schema->getType('Query');
$testInput = $schema->getType('TestInput');
$testEnum = $schema->getType('TestEnum');
$testUnion = $schema->getType('TestUnion');
$testInterface = $schema->getType('TestInterface');
$testType = $schema->getType('TestType');
$testDirective = $schema->getDirective('test');
$restoredIDL = SchemaPrinter::doPrint(BuildSchema::build(
Printer::doPrint($schema->getAstNode()) . "\n" .
Printer::doPrint($query->astNode) . "\n" .
Printer::doPrint($testInput->astNode) . "\n" .
Printer::doPrint($testEnum->astNode) . "\n" .
Printer::doPrint($testUnion->astNode) . "\n" .
Printer::doPrint($testInterface->astNode) . "\n" .
Printer::doPrint($testType->astNode) . "\n" .
Printer::doPrint($testDirective->astNode)
));
$this->assertEquals($restoredIDL, SchemaPrinter::doPrint($schema));
$testField = $query->getField('testField');
$this->assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode));
$this->assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode));
$this->assertEquals('testInputField: TestEnum', Printer::doPrint($testInput->getField('testInputField')->astNode));
$this->assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode));
$this->assertEquals('interfaceField: String', Printer::doPrint($testInterface->getField('interfaceField')->astNode));
$this->assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode));
$this->assertEquals('arg: Int', Printer::doPrint($testDirective->args[0]->astNode));
}
// Describe: Failures
/**
@ -973,7 +1036,7 @@ interface Hello {
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
$this->assertInstanceOf(\Closure::class, $defaultConfig['interfaces']);
$this->assertArrayHasKey('description', $defaultConfig);
$this->assertCount(4, $defaultConfig);
$this->assertCount(5, $defaultConfig);
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Query', $schema->getType('Query')->description);
@ -985,12 +1048,12 @@ interface Hello {
'description' => '',
'deprecationReason' => ''
];
$this->assertEquals([
$this->assertArraySubset([
'RED' => $enumValue,
'GREEN' => $enumValue,
'BLUE' => $enumValue,
], $defaultConfig['values']);
$this->assertCount(3, $defaultConfig);
$this->assertCount(4, $defaultConfig); // 3 + astNode
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Color', $schema->getType('Color')->description);
@ -1000,7 +1063,7 @@ interface Hello {
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
$this->assertInstanceOf(\Closure::class, $defaultConfig['resolveType']);
$this->assertArrayHasKey('description', $defaultConfig);
$this->assertCount(4, $defaultConfig);
$this->assertCount(5, $defaultConfig);
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Hello', $schema->getType('Hello')->description);
}