diff --git a/composer.json b/composer.json index 52c64fbdd1f..97db1f467ae 100644 --- a/composer.json +++ b/composer.json @@ -67,7 +67,7 @@ "symfony/validator": "^3.3 || ^4.0", "symfony/web-profiler-bundle": "^3.3 || ^4.0", "symfony/yaml": "^3.3 || ^4.0", - "webonyx/graphql-php": "^0.10.2" + "webonyx/graphql-php": "^0.11.5" }, "conflict": { "symfony/dependency-injection": "<3.4" diff --git a/features/bootstrap/GraphqlContext.php b/features/bootstrap/GraphqlContext.php index fc31e4efc3c..fb9d94c701a 100644 --- a/features/bootstrap/GraphqlContext.php +++ b/features/bootstrap/GraphqlContext.php @@ -17,6 +17,7 @@ use Behat\Gherkin\Node\PyStringNode; use Behatch\Context\RestContext; use Behatch\HttpCall\Request; +use GraphQL\Type\Introspection; /** * Context for GraphQL. @@ -95,6 +96,15 @@ public function ISendTheGraphqlRequestWithOperation(string $operation) $this->sendGraphqlRequest(); } + /** + * @When I send the query to introspect the schema + */ + public function ISendTheQueryToIntrospectTheSchema() + { + $this->graphqlRequest = ['query' => Introspection::getIntrospectionQuery()]; + $this->sendGraphqlRequest(); + } + private function sendGraphqlRequest() { $this->request->setHttpHeader('Accept', null); diff --git a/features/graphql/introspection.feature b/features/graphql/introspection.feature index 063519d60e2..e0551d9b1eb 100644 --- a/features/graphql/introspection.feature +++ b/features/graphql/introspection.feature @@ -9,20 +9,13 @@ Feature: GraphQL introspection support And the JSON node "errors[0].message" should be equal to "GraphQL query is not valid" Scenario: Introspect the GraphQL schema - When I send the following GraphQL request: - """ - { - __schema { - types { - name - } - } - } - """ + When I send the query to introspect the schema Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/json" And the JSON node "data.__schema.types" should exist + And the JSON node "data.__schema.queryType.name" should be equal to "Query" + And the JSON node "data.__schema.mutationType.name" should be equal to "Mutation" Scenario: Introspect types When I send the following GraphQL request: diff --git a/features/graphql/mutation.feature b/features/graphql/mutation.feature index 0e08b13fd2c..1786fc9ecb7 100644 --- a/features/graphql/mutation.feature +++ b/features/graphql/mutation.feature @@ -106,44 +106,6 @@ Feature: GraphQL mutation support And the JSON node "data.createDummy.arrayData[1]" should be equal to baz And the JSON node "data.createDummy.clientMutationId" should be equal to "myId" - Scenario: Create an item with an embedded field - When I send the following GraphQL request: - """ - mutation { - createRelatedDummy(input: {_id: 2, symfony: "symfony", embeddedDummy: {dummyName: "Embedded"}, clientMutationId: "myId"}) { - id - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createRelatedDummy.id" should be equal to "/related_dummies/2" - And the JSON node "data.createRelatedDummy.clientMutationId" should be equal to "myId" - - Scenario: Create an item and update a nested resource through a mutation - When I send the following GraphQL request: - """ - mutation { - createRelationEmbedder(input: {paris: "paris", krondstadt: "Krondstadt", anotherRelated: {id: 2, symfony: "laravel"}, clientMutationId: "myId"}) { - id - anotherRelated { - id - symfony - } - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createRelationEmbedder.id" should be equal to "/relation_embedders/1" - And the JSON node "data.createRelationEmbedder.anotherRelated.id" should be equal to "/related_dummies/2" - And the JSON node "data.createRelationEmbedder.anotherRelated.symfony" should be equal to "laravel" - And the JSON node "data.createRelationEmbedder.clientMutationId" should be equal to "myId" - Scenario: Delete an item through a mutation When I send the following GraphQL request: """ diff --git a/src/GraphQl/Type/Definition/InputUnionType.php b/src/GraphQl/Type/Definition/InputUnionType.php deleted file mode 100644 index de5a70c96b7..00000000000 --- a/src/GraphQl/Type/Definition/InputUnionType.php +++ /dev/null @@ -1,187 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\GraphQl\Type\Definition; - -use GraphQL\Error\Error; -use GraphQL\Error\InvariantViolation; -use GraphQL\Type\Definition\InputObjectType; -use GraphQL\Type\Definition\InputType; -use GraphQL\Type\Definition\LeafType; -use GraphQL\Type\Definition\Type; -use GraphQL\Utils\Utils; - -/** - * Represents an union of other input types. - * - * @experimental - * - * @author Alan Poulain - */ -final class InputUnionType extends Type implements InputType, LeafType -{ - /** - * @var InputObjectType[] - */ - private $types; - - /** - * @var array - */ - private $config; - - /** - * @throws InvariantViolation - */ - public function __construct(array $config) - { - if (!isset($config['name'])) { - $config['name'] = $this->tryInferName(); - } - - Utils::assertValidName($config['name']); - - $this->name = $config['name']; - $this->description = $config['description'] ?? null; - $this->config = $config; - } - - /** - * @throws InvariantViolation - * - * @return InputObjectType[] - */ - public function getTypes(): array - { - if (null !== $this->types) { - return $this->types; - } - - if (($types = $this->config['types'] ?? null) && \is_callable($types)) { - $types = \call_user_func($this->config['types']); - } - - if (!\is_array($types)) { - throw new InvariantViolation( - "{$this->name} types must be an Array or a callable which returns an Array." - ); - } - - return $this->types = $types; - } - - /** - * {@inheritdoc} - */ - public function assertValid() - { - parent::assertValid(); - - $types = $this->getTypes(); - Utils::invariant(\count($types) > 0, "{$this->name} types must not be empty"); - - $includedTypeNames = []; - foreach ($types as $inputType) { - Utils::invariant( - $inputType instanceof InputType, - "{$this->name} may only contain input types, it cannot contain: %s.", - Utils::printSafe($inputType) - ); - Utils::invariant( - !isset($includedTypeNames[$inputType->name]), - "{$this->name} can include {$inputType->name} type only once." - ); - $includedTypeNames[$inputType->name] = true; - } - } - - /** - * {@inheritdoc} - * - * @throws InvariantViolation - */ - public function serialize($value) - { - foreach ($this->getTypes() as $type) { - if ($type instanceof LeafType) { - try { - return $type->serialize($value); - } catch (\Exception $e) { - } - } - } - - throw new InvariantViolation(sprintf('Types in union cannot represent value: %s', Utils::printSafe($value))); - } - - /** - * {@inheritdoc} - * - * @throws Error - */ - public function parseValue($value) - { - foreach ($this->getTypes() as $type) { - if ($type instanceof LeafType) { - try { - return $type->parseValue($value); - } catch (\Exception $e) { - } - } - } - - throw new Error(sprintf('Types in union cannot represent value: %s', Utils::printSafeJson($value))); - } - - /** - * {@inheritdoc} - */ - public function parseLiteral($valueNode) - { - foreach ($this->getTypes() as $type) { - if ($type instanceof LeafType && null !== $parsed = $type->parseLiteral($valueNode)) { - return $parsed; - } - } - - return null; - } - - /** - * {@inheritdoc} - */ - public function isValidValue($value): bool - { - foreach ($this->getTypes() as $type) { - if ($type instanceof LeafType && $type->isValidValue($value)) { - return true; - } - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function isValidLiteral($valueNode): bool - { - foreach ($this->getTypes() as $type) { - if ($type instanceof LeafType && $type->isValidLiteral($valueNode)) { - return true; - } - } - - return false; - } -} diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 34192e0955f..838e8c80743 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -16,7 +16,6 @@ use ApiPlatform\Core\Exception\ResourceClassNotFoundException; use ApiPlatform\Core\GraphQl\Resolver\Factory\ResolverFactoryInterface; use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; -use ApiPlatform\Core\GraphQl\Type\Definition\InputUnionType; use ApiPlatform\Core\GraphQl\Type\Definition\IterableType; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; @@ -343,24 +342,13 @@ private function convertType(Type $type, bool $input = false, string $mutationNa break; case Type::BUILTIN_TYPE_ARRAY: case Type::BUILTIN_TYPE_ITERABLE: - $graphqlType = $this->getIterableType(); + if (!isset($this->graphqlTypes['#iterable'])) { + $this->graphqlTypes['#iterable'] = new IterableType(); + } + $graphqlType = $this->graphqlTypes['#iterable']; break; case Type::BUILTIN_TYPE_OBJECT: - if ($input && $depth > 0) { - if (!isset($this->graphqlTypes['#stringIterableUnionInput'])) { - $this->graphqlTypes['#stringIterableUnionInput'] = new InputUnionType([ - 'name' => 'StringIterableUnionInput', - 'description' => 'Resource\'s IRI or data (embedded entities or when updating a related existing resource)', - 'types' => [ - GraphQLType::string(), - $this->getIterableType(), - ], - ]); - } - $graphqlType = $this->graphqlTypes['#stringIterableUnionInput']; - break; - } - if (is_a($type->getClassName(), \DateTimeInterface::class, true)) { + if (($input && $depth > 0) || is_a($type->getClassName(), \DateTimeInterface::class, true)) { $graphqlType = GraphQLType::string(); break; } @@ -511,15 +499,6 @@ private function getResourcePaginatedCollectionType(string $resourceClass, Graph return $this->graphqlTypes[$resourceClass]['connection'] = new ObjectType($configuration); } - private function getIterableType(): IterableType - { - if (!isset($this->graphqlTypes['#iterable'])) { - $this->graphqlTypes['#iterable'] = new IterableType(); - } - - return $this->graphqlTypes['#iterable']; - } - private function isCollection(Type $type): bool { return $type->isCollection() && Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType(); diff --git a/tests/GraphQl/Type/Definition/InputUnionTypeTest.php b/tests/GraphQl/Type/Definition/InputUnionTypeTest.php deleted file mode 100644 index c76560cac3e..00000000000 --- a/tests/GraphQl/Type/Definition/InputUnionTypeTest.php +++ /dev/null @@ -1,208 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Tests\GraphQl\Type\Definition; - -use ApiPlatform\Core\GraphQl\Type\Definition\InputUnionType; -use GraphQL\Error\Error; -use GraphQL\Error\InvariantViolation; -use GraphQL\Language\AST\StringValueNode; -use GraphQL\Type\Definition\LeafType; -use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\StringType; -use PHPUnit\Framework\TestCase; - -/** - * @author Alan Poulain - */ -class InputUnionTypeTest extends TestCase -{ - public function testGetTypesNotSet() - { - $inputUnionType = new InputUnionType([]); - - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('InputUnion types must be an Array or a callable which returns an Array.'); - - $inputUnionType->getTypes(); - } - - public function testGetTypesInvalid() - { - $inputUnionType = new InputUnionType(['types' => 1]); - - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('InputUnion types must be an Array or a callable which returns an Array.'); - - $inputUnionType->getTypes(); - } - - public function testGetTypesCallable() - { - $inputUnionType = new InputUnionType(['types' => function () { - return ['foo']; - }]); - - $this->assertEquals(['foo'], $inputUnionType->getTypes()); - } - - public function testGetTypes() - { - $inputUnionType = new InputUnionType(['types' => ['bar']]); - - $this->assertEquals(['bar'], $inputUnionType->getTypes()); - } - - public function testAssertValidEmptyTypes() - { - $inputUnionType = new InputUnionType(['types' => []]); - - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('InputUnion types must not be empty'); - - $inputUnionType->assertValid(); - } - - public function testAssertValidNotInputObjectTypes() - { - $inputUnionType = new InputUnionType(['types' => ['foo']]); - - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('InputUnion may only contain input types, it cannot contain: "foo".'); - - $inputUnionType->assertValid(); - } - - public function testAssertValidDuplicateTypes() - { - $type = $this->prophesize(StringType::class)->reveal(); - $inputUnionType = new InputUnionType(['types' => [$type, $type]]); - - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('InputUnion can include String type only once.'); - - $inputUnionType->assertValid(); - } - - public function testSerializeNotLeafType() - { - $type = $this->prophesize(ObjectType::class)->reveal(); - $inputUnionType = new InputUnionType(['types' => [$type]]); - - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('Types in union cannot represent value: "foo"'); - - $inputUnionType->serialize('foo'); - } - - public function testSerialize() - { - $type = $this->prophesize(LeafType::class); - $type->serialize('foo')->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $inputUnionType->serialize('foo'); - } - - public function testParseValueNotLeafType() - { - $type = $this->prophesize(ObjectType::class)->reveal(); - $inputUnionType = new InputUnionType(['types' => [$type]]); - - $this->expectException(Error::class); - $this->expectExceptionMessage('Types in union cannot represent value: "foo"'); - - $inputUnionType->parseValue('foo'); - } - - public function testParseValue() - { - $type = $this->prophesize(LeafType::class); - $type->parseValue('foo')->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $inputUnionType->parseValue('foo'); - } - - public function testParseLiteralNotLeafType() - { - $type = $this->prophesize(ObjectType::class)->reveal(); - $inputUnionType = new InputUnionType(['types' => [$type]]); - - $this->assertNull($inputUnionType->parseLiteral(new StringValueNode(['value' => 'foo']))); - } - - public function testParseLiteral() - { - $type = $this->prophesize(LeafType::class); - $node = new StringValueNode(['value' => 'foo']); - $type->parseLiteral($node)->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $inputUnionType->parseLiteral($node); - } - - public function testIsValidValueNotLeafType() - { - $type = $this->prophesize(ObjectType::class)->reveal(); - $inputUnionType = new InputUnionType(['types' => [$type]]); - - $this->assertFalse($inputUnionType->isValidValue('foo')); - } - - public function testIsValidValueInvalid() - { - $type = $this->prophesize(LeafType::class); - $type->isValidValue('foo')->willReturn(false)->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $this->assertFalse($inputUnionType->isValidValue('foo')); - } - - public function testIsValidValue() - { - $type = $this->prophesize(LeafType::class); - $type->isValidValue('foo')->willReturn(true)->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $this->assertTrue($inputUnionType->isValidValue('foo')); - } - - public function testIsValidLiteralNotLeafType() - { - $type = $this->prophesize(ObjectType::class)->reveal(); - $inputUnionType = new InputUnionType(['types' => [$type]]); - - $this->assertFalse($inputUnionType->isValidLiteral(new StringValueNode(['value' => 'foo']))); - } - - public function testIsValidLiteralInvalid() - { - $type = $this->prophesize(LeafType::class); - $node = new StringValueNode(['value' => 'foo']); - $type->isValidLiteral($node)->willReturn(false)->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $this->assertFalse($inputUnionType->isValidLiteral($node)); - } - - public function testIsValidLiteral() - { - $type = $this->prophesize(LeafType::class); - $node = new StringValueNode(['value' => 'foo']); - $type->isValidLiteral($node)->willReturn(true)->shouldBeCalled(); - $inputUnionType = new InputUnionType(['types' => [$type->reveal()]]); - - $this->assertTrue($inputUnionType->isValidLiteral($node)); - } -}