diff --git a/src/Type/Definition/CustomScalarType.php b/src/Type/Definition/CustomScalarType.php index 4740b29..49bc8be 100644 --- a/src/Type/Definition/CustomScalarType.php +++ b/src/Type/Definition/CustomScalarType.php @@ -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 diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index f1c8c55..546f02d 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -1,6 +1,8 @@ $value) { $this->{$key} = $value; } + $this->config = $config; } } diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index b320bd5..120bfee 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -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) { diff --git a/src/Type/Definition/EnumValueDefinition.php b/src/Type/Definition/EnumValueDefinition.php index 12bbbe1..cd94bab 100644 --- a/src/Type/Definition/EnumValueDefinition.php +++ b/src/Type/Definition/EnumValueDefinition.php @@ -1,5 +1,6 @@ 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; } diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index 3385822..67a567f 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -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; diff --git a/src/Type/Definition/FieldDefinition.php b/src/Type/Definition/FieldDefinition.php index e3b0ced..8da5b05 100644 --- a/src/Type/Definition/FieldDefinition.php +++ b/src/Type/Definition/FieldDefinition.php @@ -1,6 +1,8 @@ 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; diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index 732baee..1744e51 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -1,5 +1,6 @@ config = $config; $this->name = $config['name']; + $this->astNode = isset($config['astNode']) ? $config['astNode'] : null; $this->description = isset($config['description']) ? $config['description'] : null; } diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index 1183f32..9d532ec 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -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; } diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index f2b9d70..43d2355 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -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; } diff --git a/src/Type/Definition/ScalarType.php b/src/Type/Definition/ScalarType.php index 0c05f99..fde81aa 100644 --- a/src/Type/Definition/ScalarType.php +++ b/src/Type/Definition/ScalarType.php @@ -1,6 +1,7 @@ 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); } diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index aa52df2..2b2c83f 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -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 */ diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index ebec1b6..49855b7 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -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; } diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 10f1627..dfdebb5 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -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 diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index 928b993..2b03c37 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -1,6 +1,7 @@ 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 diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 6ebd6c9..d75a11f 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -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, ]; } diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 300fc72..86439e1 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -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, ], ]; diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 475983e..cbe1e7a 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -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); }