From 5ac8dc9624ad1f31a4b191cada5e24b9a15791fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 1 Sep 2021 11:55:15 +0200 Subject: [PATCH] [Serializer] Throw NotNormalizableValueException when type is not known or not in body in discriminator map --- .../Normalizer/AbstractObjectNormalizer.php | 5 +-- .../Serializer/Tests/Fixtures/Php74Full.php | 1 + .../Serializer/Tests/SerializerTest.php | 44 +++++++++++++++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 1665fdfe5cc86..dc66d9139085a 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -22,7 +22,6 @@ use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; -use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; @@ -276,12 +275,12 @@ protected function instantiateObject(array &$data, string $class, array &$contex { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { if (!isset($data[$mapping->getTypeProperty()])) { - throw new RuntimeException(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class)); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); } $type = $data[$mapping->getTypeProperty()]; if (null === ($mappedClass = $mapping->getClassForType($type))) { - throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s".', $type, $class)); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true); } if ($mappedClass !== $class) { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php index 496f722af925c..4f3186c30e94b 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php @@ -28,6 +28,7 @@ final class Php74Full /** @var Php74Full[] */ public array $collection; public Php74FullWithConstructor $php74FullWithConstructor; + public DummyMessageInterface $dummyMessage; } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 559c1037debfe..97252bcff7049 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -24,7 +24,6 @@ use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; -use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; @@ -484,16 +483,34 @@ public function testDeserializeAndSerializeNestedInterfacedObjectsWithTheClassMe public function testExceptionWhenTypeIsNotKnownInDiscriminator() { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The type "second" has no mapped class for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface"'); - $this->serializerWithClassDiscriminator()->deserialize('{"type":"second","one":1}', DummyMessageInterface::class, 'json'); + try { + $this->serializerWithClassDiscriminator()->deserialize('{"type":"second","one":1}', DummyMessageInterface::class, 'json'); + + $this->fail(); + } catch (\Throwable $e) { + $this->assertInstanceOf(NotNormalizableValueException::class, $e); + $this->assertSame('The type "second" is not a valid value.', $e->getMessage()); + $this->assertSame('string', $e->getCurrentType()); + $this->assertSame(['string'], $e->getExpectedTypes()); + $this->assertSame('type', $e->getPath()); + $this->assertTrue($e->canUseMessageForUser()); + } } public function testExceptionWhenTypeIsNotInTheBodyToDeserialiaze() { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface"'); - $this->serializerWithClassDiscriminator()->deserialize('{"one":1}', DummyMessageInterface::class, 'json'); + try { + $this->serializerWithClassDiscriminator()->deserialize('{"one":1}', DummyMessageInterface::class, 'json'); + + $this->fail(); + } catch (\Throwable $e) { + $this->assertInstanceOf(NotNormalizableValueException::class, $e); + $this->assertSame('Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface".', $e->getMessage()); + $this->assertSame('null', $e->getCurrentType()); + $this->assertSame(['string'], $e->getExpectedTypes()); + $this->assertSame('type', $e->getPath()); + $this->assertFalse($e->canUseMessageForUser()); + } } public function testNotNormalizableValueExceptionMessageForAResource() @@ -744,7 +761,9 @@ public function testCollectDenormalizationErrors() "string": null } ], - "php74FullWithConstructor": {} + "php74FullWithConstructor": {}, + "dummyMessage": { + } }'; $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -893,6 +912,15 @@ public function testCollectDenormalizationErrors() 'useMessageForUser' => true, 'message' => 'Failed to create object because the object miss the "constructorArgument" property.', ], + [ + 'currentType' => 'null', + 'expectedTypes' => [ + 'string', + ], + 'path' => 'dummyMessage.type', + 'useMessageForUser' => false, + 'message' => 'Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface".', + ], ]; $this->assertSame($expected, $exceptionsAsArray);