Skip to content

Commit

Permalink
fix(graphql): support nullable embedded relations in GraphQL types
Browse files Browse the repository at this point in the history
  • Loading branch information
Koenstell committed Jan 10, 2024
1 parent 804da1b commit 384b648
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 11 deletions.
24 changes: 24 additions & 0 deletions src/GraphQl/Tests/Type/TypeBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,30 @@ public function testGetResourceObjectTypeNestedInput(): void
$wrappedType->config['fields']();
}

public function testGetResourceObjectTypeNestedInputNullable(): void

Check warning on line 220 in src/GraphQl/Tests/Type/TypeBuilderTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeBuilderTest.php#L220

Added line #L220 was not covered by tests
{
$resourceMetadata = new ResourceMetadataCollection('resourceClass', []);
$this->typesContainerProphecy->has('customShortNameNullableNestedInput')->shouldBeCalled()->willReturn(false);
$this->typesContainerProphecy->set('customShortNameNullableNestedInput', Argument::type(InputObjectType::class))->shouldBeCalled();
$this->typesContainerProphecy->has('Node')->shouldBeCalled()->willReturn(false);
$this->typesContainerProphecy->set('Node', Argument::type(InterfaceType::class))->shouldBeCalled();

Check warning on line 226 in src/GraphQl/Tests/Type/TypeBuilderTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeBuilderTest.php#L222-L226

Added lines #L222 - L226 were not covered by tests

/** @var Operation $operation */
$operation = (new Mutation())->withName('custom')->withShortName('shortNameNullable')->withDescription('description nullable');

Check warning on line 229 in src/GraphQl/Tests/Type/TypeBuilderTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeBuilderTest.php#L229

Added line #L229 was not covered by tests
/** @var InputObjectType $resourceObjectType */
$resourceObjectType = $this->typeBuilder->getResourceObjectType('resourceClass', $resourceMetadata, $operation, true, false, 1, false);

Check warning on line 231 in src/GraphQl/Tests/Type/TypeBuilderTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeBuilderTest.php#L231

Added line #L231 was not covered by tests

$this->assertInstanceOf(InputObjectType::class, $resourceObjectType);
$this->assertSame('customShortNameNullableNestedInput', $resourceObjectType->name);
$this->assertSame('description nullable', $resourceObjectType->description);
$this->assertArrayHasKey('fields', $resourceObjectType->config);

Check warning on line 236 in src/GraphQl/Tests/Type/TypeBuilderTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeBuilderTest.php#L233-L236

Added lines #L233 - L236 were not covered by tests

$fieldsBuilderProphecy = $this->prophesize(FieldsBuilderEnumInterface::class);
$fieldsBuilderProphecy->getResourceObjectTypeFields('resourceClass', $operation, true, 1, null)->shouldBeCalled();
$this->fieldsBuilderLocatorProphecy->get('api_platform.graphql.fields_builder')->shouldBeCalled()->willReturn($fieldsBuilderProphecy->reveal());
$resourceObjectType->config['fields']();

Check warning on line 241 in src/GraphQl/Tests/Type/TypeBuilderTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeBuilderTest.php#L238-L241

Added lines #L238 - L241 were not covered by tests
}

public function testGetResourceObjectTypeCustomMutationInputArgs(): void
{
$resourceMetadata = new ResourceMetadataCollection('resourceClass', []);
Expand Down
4 changes: 2 additions & 2 deletions src/GraphQl/Tests/Type/TypeConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public function testConvertTypeInputResource(): void
$this->resourceMetadataCollectionFactoryProphecy->create('dummy')->willReturn($graphqlResourceMetadata);
$this->typeBuilderProphecy->isCollection($type)->willReturn(false);
$this->propertyMetadataFactoryProphecy->create('rootClass', 'dummyProperty', Argument::type('array'))->shouldBeCalled()->willReturn((new ApiProperty())->withWritableLink(true));
$this->typeBuilderProphecy->getResourceObjectType('dummy', $graphqlResourceMetadata, $operation, true, false, 1)->shouldBeCalled()->willReturn($expectedGraphqlType);
$this->typeBuilderProphecy->getResourceObjectType('dummy', $graphqlResourceMetadata, $operation, true, false, 1, true)->shouldBeCalled()->willReturn($expectedGraphqlType);

Check warning on line 164 in src/GraphQl/Tests/Type/TypeConverterTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeConverterTest.php#L164

Added line #L164 was not covered by tests

$graphqlType = $this->typeConverter->convertType($type, true, $operation, 'dummy', 'rootClass', 'dummyProperty', 1);
$this->assertSame($expectedGraphqlType, $graphqlType);
Expand All @@ -179,7 +179,7 @@ public function testConvertTypeCollectionResource(Type $type, ObjectType $expect

$this->typeBuilderProphecy->isCollection($type)->shouldBeCalled()->willReturn(true);
$this->resourceMetadataCollectionFactoryProphecy->create('dummyValue')->shouldBeCalled()->willReturn($graphqlResourceMetadata);
$this->typeBuilderProphecy->getResourceObjectType('dummyValue', $graphqlResourceMetadata, $collectionOperation, false, false, 0)->shouldBeCalled()->willReturn($expectedGraphqlType);
$this->typeBuilderProphecy->getResourceObjectType('dummyValue', $graphqlResourceMetadata, $collectionOperation, false, false, 0, true)->shouldBeCalled()->willReturn($expectedGraphqlType);

Check warning on line 182 in src/GraphQl/Tests/Type/TypeConverterTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Type/TypeConverterTest.php#L182

Added line #L182 was not covered by tests

/** @var Operation $rootOperation */
$rootOperation = (new Query())->withName('test');
Expand Down
16 changes: 12 additions & 4 deletions src/GraphQl/Type/TypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function __construct(private readonly TypesContainerInterface $typesConta
/**
* {@inheritdoc}
*/
public function getResourceObjectType(?string $resourceClass, ResourceMetadataCollection $resourceMetadataCollection, Operation $operation, bool $input, bool $wrapped = false, int $depth = 0): GraphQLType
public function getResourceObjectType(?string $resourceClass, ResourceMetadataCollection $resourceMetadataCollection, Operation $operation, bool $input, bool $wrapped = false, int $depth = 0, bool $required = true): GraphQLType

Check warning on line 51 in src/GraphQl/Type/TypeBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Type/TypeBuilder.php#L51

Added line #L51 was not covered by tests
{
$shortName = $operation->getShortName();
$operationName = $operation->getName();
Expand Down Expand Up @@ -86,8 +86,8 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo

if ($this->typesContainer->has($shortName)) {
$resourceObjectType = $this->typesContainer->get($shortName);
if (!($resourceObjectType instanceof ObjectType || $resourceObjectType instanceof NonNull)) {
throw new \LogicException(sprintf('Expected GraphQL type "%s" to be %s.', $shortName, implode('|', [ObjectType::class, NonNull::class])));
if (!($resourceObjectType instanceof ObjectType || $resourceObjectType instanceof NonNull || $resourceObjectType instanceof InputObjectType)) {
throw new \LogicException(sprintf('Expected GraphQL type "%s" to be %s.', $shortName, implode('|', [ObjectType::class, NonNull::class, InputObjectType::class])));

Check warning on line 90 in src/GraphQl/Type/TypeBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Type/TypeBuilder.php#L89-L90

Added lines #L89 - L90 were not covered by tests
}

return $resourceObjectType;
Expand Down Expand Up @@ -156,7 +156,15 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo
'interfaces' => $wrapData ? [] : [$this->getNodeInterface()],
];

$resourceObjectType = $input ? GraphQLType::nonNull(new InputObjectType($configuration)) : new ObjectType($configuration);
if ($input) {
$resourceObjectType = new InputObjectType($configuration);
if ($required) {
$resourceObjectType = GraphQLType::nonNull($resourceObjectType);

Check warning on line 162 in src/GraphQl/Type/TypeBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Type/TypeBuilder.php#L159-L162

Added lines #L159 - L162 were not covered by tests
}
} else {
$resourceObjectType = new ObjectType($configuration);

Check warning on line 165 in src/GraphQl/Type/TypeBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Type/TypeBuilder.php#L165

Added line #L165 was not covered by tests
}

$this->typesContainer->set($shortName, $resourceObjectType);

return $resourceObjectType;
Expand Down
6 changes: 2 additions & 4 deletions src/GraphQl/Type/TypeBuilderEnumInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type as GraphQLType;
use Symfony\Component\PropertyInfo\Type;

Expand All @@ -31,9 +29,9 @@ interface TypeBuilderEnumInterface
/**
* Gets the object type of the given resource.
*
* @return ObjectType|NonNull the object type, possibly wrapped by NonNull
* @return GraphQLType the object type, possibly wrapped by NonNull
*/
public function getResourceObjectType(?string $resourceClass, ResourceMetadataCollection $resourceMetadataCollection, Operation $operation, bool $input, bool $wrapped = false, int $depth = 0): GraphQLType;
public function getResourceObjectType(?string $resourceClass, ResourceMetadataCollection $resourceMetadataCollection, Operation $operation, bool $input, bool $wrapped = false, int $depth = 0, bool $required = false): GraphQLType;

/**
* Get the interface type of a node.
Expand Down
4 changes: 3 additions & 1 deletion src/GraphQl/Type/TypeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ private function getResourceType(Type $type, bool $input, Operation $rootOperati
throw new OperationNotFoundException();
}

return $this->typeBuilder->getResourceObjectType($resourceClass, $resourceMetadataCollection, $operation, $input, false, $depth);
$required = $propertyMetadata?->isRequired() ?? true;

Check warning on line 185 in src/GraphQl/Type/TypeConverter.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Type/TypeConverter.php#L185

Added line #L185 was not covered by tests

return $this->typeBuilder->getResourceObjectType($resourceClass, $resourceMetadataCollection, $operation, $input, false, $depth, $required);

Check warning on line 187 in src/GraphQl/Type/TypeConverter.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Type/TypeConverter.php#L187

Added line #L187 was not covered by tests
}

private function resolveAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType
Expand Down

0 comments on commit 384b648

Please sign in to comment.