diff --git a/src/Serializer/AbstractConstraintViolationListNormalizer.php b/src/Serializer/AbstractConstraintViolationListNormalizer.php index af6b8d8c099..009da3d24f3 100644 --- a/src/Serializer/AbstractConstraintViolationListNormalizer.php +++ b/src/Serializer/AbstractConstraintViolationListNormalizer.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Serializer; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -56,8 +57,17 @@ protected function getMessagesAndViolations(ConstraintViolationListInterface $co foreach ($constraintViolationList as $violation) { $class = \is_object($root = $violation->getRoot()) ? $root::class : null; + + if ($this->nameConverter instanceof AdvancedNameConverterInterface) { + $propertyPath = $this->nameConverter->normalize($violation->getPropertyPath(), $class, static::FORMAT); + } elseif ($this->nameConverter instanceof NameConverterInterface) { + $propertyPath = $this->nameConverter->normalize($violation->getPropertyPath()); + } else { + $propertyPath = $violation->getPropertyPath(); + } + $violationData = [ - 'propertyPath' => $this->nameConverter ? $this->nameConverter->normalize($violation->getPropertyPath(), $class, static::FORMAT) : $violation->getPropertyPath(), + 'propertyPath' => $propertyPath, 'message' => $violation->getMessage(), 'code' => $violation->getCode(), ]; diff --git a/tests/Hydra/Serializer/ConstraintViolationNormalizerTest.php b/tests/Hydra/Serializer/ConstraintViolationNormalizerTest.php index 9f534af6d3d..b2ee6a82983 100644 --- a/tests/Hydra/Serializer/ConstraintViolationNormalizerTest.php +++ b/tests/Hydra/Serializer/ConstraintViolationNormalizerTest.php @@ -18,6 +18,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\ConstraintViolation; @@ -44,17 +45,14 @@ public function testSupportNormalization(): void } /** - * @dataProvider payloadFieldsProvider + * @dataProvider nameConverterAndPayloadFieldsProvider */ - public function testNormalize(?array $fields, array $result): void + public function testNormalize(?object $nameConverter, ?array $fields, array $expected): void { $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); - $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); - $urlGeneratorProphecy->generate('api_jsonld_context', ['shortName' => 'ConstraintViolationList'])->willReturn('/context/foo')->shouldBeCalled(); - $nameConverterProphecy->normalize(Argument::type('string'), null, Argument::type('string'))->will(fn ($args): string => '_'.$args[0]); - $normalizer = new ConstraintViolationListNormalizer($urlGeneratorProphecy->reveal(), $fields, $nameConverterProphecy->reveal()); + $normalizer = new ConstraintViolationListNormalizer($urlGeneratorProphecy->reveal(), $fields, $nameConverter); // Note : we use NotNull constraint and not Constraint class because Constraint is abstract $constraint = new NotNull(); @@ -64,7 +62,31 @@ public function testNormalize(?array $fields, array $result): void new ConstraintViolation('1', '2', [], '3', '4', '5'), ]); - $expected = [ + $this->assertSame($expected, $normalizer->normalize($list)); + } + + public function nameConverterAndPayloadFieldsProvider(): iterable + { + $basicExpectation = [ + '@context' => '/context/foo', + '@type' => 'ConstraintViolationList', + 'hydra:title' => 'An error occurred', + 'hydra:description' => "d: a\n4: 1", + 'violations' => [ + [ + 'propertyPath' => 'd', + 'message' => 'a', + 'code' => 'f24bdbad0becef97a6887238aa58221c', + ], + [ + 'propertyPath' => '4', + 'message' => '1', + 'code' => null, + ], + ], + ]; + + $nameConverterBasedExpectation = [ '@context' => '/context/foo', '@type' => 'ConstraintViolationList', 'hydra:title' => 'An error occurred', @@ -82,17 +104,35 @@ public function testNormalize(?array $fields, array $result): void ], ], ]; - if ([] !== $result) { - $expected['violations'][0]['payload'] = $result; - } - $this->assertSame($expected, $normalizer->normalize($list)); - } + $advancedNameConverterProphecy = $this->prophesize(AdvancedNameConverterInterface::class); + $advancedNameConverterProphecy->normalize(Argument::type('string'), null, Argument::type('string'))->will(fn ($args): string => '_'.$args[0]); + $advancedNameConverter = $advancedNameConverterProphecy->reveal(); - public function payloadFieldsProvider(): iterable - { - yield [['severity', 'anotherField1'], ['severity' => 'warning']]; - yield [null, ['severity' => 'warning', 'anotherField2' => 'aValue']]; - yield [[], []]; + $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); + $nameConverterProphecy->normalize(Argument::type('string'))->will(fn ($args): string => '_'.$args[0]); + $nameConverter = $nameConverterProphecy->reveal(); + + $nullNameConverter = null; + + $expected = $nameConverterBasedExpectation; + $expected['violations'][0]['payload'] = ['severity' => 'warning']; + yield [$advancedNameConverter, ['severity', 'anotherField1'], $expected]; + yield [$nameConverter, ['severity', 'anotherField1'], $expected]; + $expected = $basicExpectation; + $expected['violations'][0]['payload'] = ['severity' => 'warning']; + yield [$nullNameConverter, ['severity', 'anotherField1'], $expected]; + + $expected = $nameConverterBasedExpectation; + $expected['violations'][0]['payload'] = ['severity' => 'warning', 'anotherField2' => 'aValue']; + yield [$advancedNameConverter, null, $expected]; + yield [$nameConverter, null, $expected]; + $expected = $basicExpectation; + $expected['violations'][0]['payload'] = ['severity' => 'warning', 'anotherField2' => 'aValue']; + yield [$nullNameConverter, null, $expected]; + + yield [$advancedNameConverter, [], $nameConverterBasedExpectation]; + yield [$nameConverter, [], $nameConverterBasedExpectation]; + yield [$nullNameConverter, [], $basicExpectation]; } } diff --git a/tests/Problem/Serializer/ConstraintViolationNormalizerTest.php b/tests/Problem/Serializer/ConstraintViolationNormalizerTest.php index 1252591f219..34f5ca61f68 100644 --- a/tests/Problem/Serializer/ConstraintViolationNormalizerTest.php +++ b/tests/Problem/Serializer/ConstraintViolationNormalizerTest.php @@ -17,6 +17,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\ConstraintViolation; @@ -40,12 +41,12 @@ public function testSupportNormalization(): void $this->assertTrue($normalizer->hasCacheableSupportsMethod()); } - public function testNormalize(): void + /** + * @dataProvider nameConverterProvider + */ + public function testNormalize(object|null $nameConverter, array $expected): void { - $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); - $normalizer = new ConstraintViolationListNormalizer(['severity', 'anotherField1'], $nameConverterProphecy->reveal()); - - $nameConverterProphecy->normalize(Argument::type('string'), null, Argument::type('string'))->will(fn ($args) => '_'.$args[0]); + $normalizer = new ConstraintViolationListNormalizer(['severity', 'anotherField1'], $nameConverter); // Note : we use NotNull constraint and not Constraint class because Constraint is abstract $constraint = new NotNull(); @@ -55,6 +56,11 @@ public function testNormalize(): void new ConstraintViolation('1', '2', [], '3', '4', '5'), ]); + $this->assertSame($expected, $normalizer->normalize($list)); + } + + public function nameConverterProvider(): iterable + { $expected = [ 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => 'An error occurred', @@ -75,6 +81,35 @@ public function testNormalize(): void ], ], ]; - $this->assertSame($expected, $normalizer->normalize($list)); + + $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); + $nameConverterProphecy->normalize(Argument::type('string'))->will(fn ($args) => '_'.$args[0]); + yield [$nameConverterProphecy->reveal(), $expected]; + + $nameConverterProphecy = $this->prophesize(AdvancedNameConverterInterface::class); + $nameConverterProphecy->normalize(Argument::type('string'), null, Argument::type('string'))->will(fn ($args) => '_'.$args[0]); + yield [$nameConverterProphecy->reveal(), $expected]; + + $expected = [ + 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => 'An error occurred', + 'detail' => "d: a\n4: 1", + 'violations' => [ + [ + 'propertyPath' => 'd', + 'message' => 'a', + 'code' => 'f24bdbad0becef97a6887238aa58221c', + 'payload' => [ + 'severity' => 'warning', + ], + ], + [ + 'propertyPath' => '4', + 'message' => '1', + 'code' => null, + ], + ], + ]; + yield [null, $expected]; } }