Skip to content

Commit

Permalink
fix(serializer): deserialization path for not denormalizable relation…
Browse files Browse the repository at this point in the history
…s collected errors
  • Loading branch information
julienfalque committed Aug 23, 2024
1 parent 41deeb4 commit 47efdc7
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 5 deletions.
16 changes: 11 additions & 5 deletions src/Serializer/AbstractItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@

use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
use ApiPlatform\Exception\InvalidArgumentException;
use ApiPlatform\Exception\InvalidArgumentException as LegacyInvalidArgumentException;
use ApiPlatform\Exception\ItemNotFoundException;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
Expand Down Expand Up @@ -244,7 +245,7 @@ public function denormalize(mixed $data, string $class, ?string $format = null,
return $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]);
} catch (ItemNotFoundException $e) {
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
} catch (InvalidArgumentException $e) {
} catch (LegacyInvalidArgumentException|InvalidArgumentException $e) {
throw new UnexpectedValueException(\sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e);
}
}
Expand Down Expand Up @@ -541,9 +542,14 @@ protected function denormalizeCollection(string $attribute, ApiProperty $propert
$childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
$collectionKeyTypes = $type->getCollectionKeyTypes();
foreach ($value as $index => $obj) {
$currentChildContext = $childContext;
if (isset($childContext['deserialization_path'])) {
$currentChildContext['deserialization_path'] = "{$childContext['deserialization_path']}[{$index}]";
}

// no typehint provided on collection key
if (!$collectionKeyTypes) {
$values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $childContext);
$values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $currentChildContext);
continue;
}

Expand All @@ -554,7 +560,7 @@ protected function denormalizeCollection(string $attribute, ApiProperty $propert
continue;
}

$values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $childContext);
$values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $currentChildContext);
continue 2;
}
throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the key "%s" must be "%s", "%s" given.', $index, $collectionKeyTypes[0]->getBuiltinType(), \gettype($index)), $index, [$collectionKeyTypes[0]->getBuiltinType()], ($context['deserialization_path'] ?? false) ? \sprintf('key(%s)', $context['deserialization_path']) : null, true);
Expand Down Expand Up @@ -590,7 +596,7 @@ protected function denormalizeRelation(string $attributeName, ApiProperty $prope
);

return null;
} catch (InvalidArgumentException $e) {
} catch (LegacyInvalidArgumentException|InvalidArgumentException $e) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException(\sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e);
}
Expand Down
51 changes: 51 additions & 0 deletions src/Serializer/Tests/AbstractItemNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\IriConverterInterface;
Expand Down Expand Up @@ -1034,6 +1035,56 @@ public function testBadRelationTypeWithExceptionToValidationErrors(): void
$this->assertNull($actual->relatedDummy);
}

public function testDeserializationPathForNotDenormalizableRelations(): void
{
$data = [
'relatedDummies' => ['wrong'],
];

$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies']));

$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
$propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn(
(new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class))])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)
);

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
$resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
$resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);

$propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);

$serializerProphecy = $this->prophesize(SerializerInterface::class);
$serializerProphecy->willImplement(DenormalizerInterface::class);

$normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [
$propertyNameCollectionFactoryProphecy->reveal(),
$propertyMetadataFactoryProphecy->reveal(),
$iriConverterProphecy->reveal(),
$resourceClassResolverProphecy->reveal(),
$propertyAccessorProphecy->reveal(),
null,
null,
[],
null,
null,
]);
$normalizer->setSerializer($serializerProphecy->reveal());

$errors = [];
$actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => &$errors]);
$this->assertCount(0, $actual->relatedDummies);
$this->assertCount(1, $errors); // @phpstan-ignore-line method.impossibleType (false positive)
$this->assertInstanceOf(NotNormalizableValueException::class, $errors[0]);
$this->assertSame('relatedDummies[0]', $errors[0]->getPath());
}

public function testInnerDocumentNotAllowed(): void
{
$this->expectException(UnexpectedValueException::class);
Expand Down

0 comments on commit 47efdc7

Please sign in to comment.