Skip to content

Commit

Permalink
fix(graphql): output creates its own type in TypeBuilder (api-platfor…
Browse files Browse the repository at this point in the history
  • Loading branch information
alanpoulain committed May 21, 2022
1 parent a15c19f commit 1f4085e
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 15 deletions.
20 changes: 20 additions & 0 deletions features/graphql/mutation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,26 @@ Feature: GraphQL mutation support
And the JSON node "data.testCustomArgumentsDummyCustomMutation.dummyCustomMutation.result" should be equal to "18"
And the JSON node "data.testCustomArgumentsDummyCustomMutation.clientMutationId" should be equal to "myId"

Scenario: Execute a custom mutation with output
When I send the following GraphQL request:
"""
mutation {
testOutputDummyCustomMutation(input: {id: "/dummy_custom_mutations/1", operandA: 9, clientMutationId: "myId"}) {
dummyCustomMutation {
baz
bat
}
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.testOutputDummyCustomMutation.dummyCustomMutation.baz" should be equal to "98"
And the JSON node "data.testOutputDummyCustomMutation.dummyCustomMutation.bat" should be equal to "9"
And the JSON node "data.testOutputDummyCustomMutation.clientMutationId" should be equal to "myId"

Scenario: Uploading a file with a custom mutation
Given I have the following file for a GraphQL request:
| name | file |
Expand Down
26 changes: 26 additions & 0 deletions features/graphql/query.feature
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,32 @@ Feature: GraphQL query support
}
"""

Scenario: Custom item query with output
Given there are 2 dummyCustomQuery objects
When I send the following GraphQL request:
"""
{
testItemOutputDummyCustomQuery(id: "/dummy_custom_queries/1",) {
baz
bat
}
}
"""
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 should be equal to:
"""
{
"data": {
"testItemOutputDummyCustomQuery": {
"baz": 46,
"bat": "Success!"
}
}
}
"""

@createSchema
Scenario: Retrieve an item with different serialization groups for item_query and collection_query
Given there are 1 dummy with different GraphQL serialization groups objects
Expand Down
11 changes: 6 additions & 5 deletions src/Core/GraphQl/Type/TypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadata $
{
$shortName = $resourceMetadata->getShortName();

$ioMetadata = $resourceMetadata->getGraphqlAttribute($subscriptionName ?? $mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
$resourceClass = $ioMetadata['class'];
$shortName = $ioMetadata['name'];
}

if (null !== $mutationName) {
$shortName = $mutationName.ucfirst($shortName);
}
Expand Down Expand Up @@ -93,11 +99,6 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadata $
return $resourceObjectType;
}

$ioMetadata = $resourceMetadata->getGraphqlAttribute($subscriptionName ?? $mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
$resourceClass = $ioMetadata['class'];
}

$wrapData = !$wrapped && (null !== $mutationName || null !== $subscriptionName) && !$input && $depth < 1;

$configuration = [
Expand Down
11 changes: 6 additions & 5 deletions src/GraphQl/Type/TypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo
$shortName = $operation->getShortName();
$operationName = $operation->getName();

$ioMetadata = $input ? $operation->getInput() : $operation->getOutput();
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
$resourceClass = $ioMetadata['class'];
$shortName = $ioMetadata['name'];
}

if ($operation instanceof Mutation) {
$shortName = $operationName.ucfirst($shortName);
}
Expand Down Expand Up @@ -102,11 +108,6 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo
return $resourceObjectType;
}

$ioMetadata = $input ? $operation->getInput() : $operation->getOutput();
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
$resourceClass = $ioMetadata['class'];
}

$wrapData = !$wrapped && ($operation instanceof Mutation || $operation instanceof Subscription) && !$input && $depth < 1;

$configuration = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Fixtures\TestBundle\DataTransformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyCustomMutation as DummyCustomMutationDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCustomMutation;

final class DummyCustomMutationDtoDataTransformer implements DataTransformerInterface
{
/**
* {@inheritdoc}
*
* @return object
*/
public function transform($object, string $to, array $context = [])
{
if ($object instanceof \Traversable) {
foreach ($object as &$value) {
$value = $this->doTransformation($value);
}

return $object;
}

return $this->doTransformation($object);
}

private function doTransformation($object): OutputDto
{
$output = new OutputDto();
$output->baz = 98;
$output->bat = $object->getOperandA();

return $output;
}

/**
* {@inheritdoc}
*/
public function supportsTransformation($object, string $to, array $context = []): bool
{
if ($object instanceof \IteratorAggregate) {
$iterator = $object->getIterator();
if ($iterator instanceof \Iterator) {
$object = $iterator->current();
}
}

return ($object instanceof DummyCustomMutation || $object instanceof DummyCustomMutationDocument) && OutputDto::class === $to;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Fixtures\TestBundle\DataTransformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyCustomQuery as DummyCustomQueryDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCustomQuery;

final class DummyCustomQueryDtoDataTransformer implements DataTransformerInterface
{
/**
* {@inheritdoc}
*
* @return object
*/
public function transform($object, string $to, array $context = [])
{
if ($object instanceof \Traversable) {
foreach ($object as &$value) {
$value = $this->doTransformation($value);
}

return $object;
}

return $this->doTransformation($object);
}

private function doTransformation($object): OutputDto
{
$output = new OutputDto();
$output->baz = 46;
$output->bat = $object->message;

return $output;
}

/**
* {@inheritdoc}
*/
public function supportsTransformation($object, string $to, array $context = []): bool
{
if ($object instanceof \IteratorAggregate) {
$iterator = $object->getIterator();
if ($iterator instanceof \Iterator) {
$object = $iterator->current();
}
}

return ($object instanceof DummyCustomQuery || $object instanceof DummyCustomQueryDocument) && OutputDto::class === $to;
}
}
5 changes: 5 additions & 0 deletions tests/Fixtures/TestBundle/Document/DummyCustomMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Serializer\Annotation\Groups;

Expand Down Expand Up @@ -50,6 +51,10 @@
* "testCustomArguments"={
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
* "args"={"operandC"={"type"="Int!"}}
* },
* "testOutput"={
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
* "output"=OutputDto::class
* }
* })
*
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/TestBundle/Document/DummyCustomQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
Expand Down Expand Up @@ -47,6 +48,10 @@
* "customArgumentCustomType"={"type"="DateTime!"}
* }
* },
* "testItemOutput"={
* "item_query"="app.graphql.query_resolver.dummy_custom_item",
* "output"=OutputDto::class
* },
* "testCollection"={
* "collection_query"="app.graphql.query_resolver.dummy_custom_collection"
* },
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyCustomMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

Expand Down Expand Up @@ -50,6 +51,10 @@
* "testCustomArguments"={
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
* "args"={"operandC"={"type"="Int!"}}
* },
* "testOutput"={
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
* "output"=OutputDto::class
* }
* })
*
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyCustomQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
use Doctrine\ORM\Mapping as ORM;

/**
Expand Down Expand Up @@ -47,6 +48,10 @@
* "customArgumentCustomType"={"type"="DateTime!"}
* }
* },
* "testItemOutput"={
* "item_query"="app.graphql.query_resolver.dummy_custom_item",
* "output"=OutputDto::class
* },
* "testCollection"={
* "collection_query"="app.graphql.query_resolver.dummy_custom_collection"
* },
Expand Down
12 changes: 12 additions & 0 deletions tests/Fixtures/app/config/config_common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,18 @@ services:
tags:
- { name: 'api_platform.data_transformer' }

app.data_transformer.dummy_custom_query_dto:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer\DummyCustomQueryDtoDataTransformer'
public: false
tags:
- { name: 'api_platform.data_transformer' }

app.data_transformer.dummy_custom_mutation_dto:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer\DummyCustomMutationDtoDataTransformer'
public: false
tags:
- { name: 'api_platform.data_transformer' }

app.data_transformer.custom_output_dto_fallback_same_class:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer\OutputDtoSameClassTransformer'
public: false
Expand Down
10 changes: 5 additions & 5 deletions tests/GraphQl/Type/TypeBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,23 +108,23 @@ public function testGetResourceObjectType(): void
public function testGetResourceObjectTypeOutputClass(): void
{
$resourceMetadata = new ResourceMetadataCollection('resourceClass', []);
$this->typesContainerProphecy->has('shortName')->shouldBeCalled()->willReturn(false);
$this->typesContainerProphecy->set('shortName', Argument::type(ObjectType::class))->shouldBeCalled();
$this->typesContainerProphecy->has('outputName')->shouldBeCalled()->willReturn(false);
$this->typesContainerProphecy->set('outputName', Argument::type(ObjectType::class))->shouldBeCalled();
$this->typesContainerProphecy->has('Node')->shouldBeCalled()->willReturn(false);
$this->typesContainerProphecy->set('Node', Argument::type(InterfaceType::class))->shouldBeCalled();

/** @var Operation $operation */
$operation = (new Query())->withShortName('shortName')->withDescription('description')->withOutput(['class' => 'outputClass']);
$operation = (new Query())->withShortName('shortName')->withDescription('description')->withOutput(['class' => 'outputClass', 'name' => 'outputName']);
/** @var ObjectType $resourceObjectType */
$resourceObjectType = $this->typeBuilder->getResourceObjectType('resourceClass', $resourceMetadata, $operation, false);
$this->assertSame('shortName', $resourceObjectType->name);
$this->assertSame('outputName', $resourceObjectType->name);
$this->assertSame('description', $resourceObjectType->description);
$this->assertSame($this->defaultFieldResolver, $resourceObjectType->resolveFieldFn);
$this->assertArrayHasKey('interfaces', $resourceObjectType->config);
$this->assertArrayHasKey('fields', $resourceObjectType->config);

$fieldsBuilderProphecy = $this->prophesize(FieldsBuilderInterface::class);
$fieldsBuilderProphecy->getResourceObjectTypeFields('outputClass', $operation, false, 0, ['class' => 'outputClass'])->shouldBeCalled();
$fieldsBuilderProphecy->getResourceObjectTypeFields('outputClass', $operation, false, 0, ['class' => 'outputClass', 'name' => 'outputName'])->shouldBeCalled();
$this->fieldsBuilderLocatorProphecy->get('api_platform.graphql.fields_builder')->shouldBeCalled()->willReturn($fieldsBuilderProphecy->reveal());
$resourceObjectType->config['fields']();
}
Expand Down

0 comments on commit 1f4085e

Please sign in to comment.