From c9b9cacf570fb2c0bffbc1a050807f4410e981d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Thu, 14 Nov 2024 14:10:34 +0100 Subject: [PATCH] Introduced StructValidator to unpack validation errors for structs --- src/bundle/Core/Resources/config/services.yml | 3 ++- .../AbstractValidationStructWrapper.php | 2 ++ src/contracts/Validation/StructValidator.php | 4 +++- .../ValidatorStructWrapperInterface.php | 8 ++++++++ tests/lib/Validation/StructValidatorTest.php | 18 ++++++++++++------ 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index 782a112a19..9d3a73cf15 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -373,6 +373,7 @@ services: Ibexa\Contracts\Core\Validation\StructValidator: decorates: 'validator' - decoration_priority: -10 + # Decorator priority is higher than debug.validator to ensure profiler receives struct errors + decoration_priority: 500 arguments: $inner: '@.inner' diff --git a/src/contracts/Validation/AbstractValidationStructWrapper.php b/src/contracts/Validation/AbstractValidationStructWrapper.php index d96085eafa..dacc3543d4 100644 --- a/src/contracts/Validation/AbstractValidationStructWrapper.php +++ b/src/contracts/Validation/AbstractValidationStructWrapper.php @@ -10,6 +10,8 @@ /** * @template T of object + * + * @implements \Ibexa\Contracts\Core\Validation\ValidatorStructWrapperInterface */ abstract class AbstractValidationStructWrapper implements ValidatorStructWrapperInterface { diff --git a/src/contracts/Validation/StructValidator.php b/src/contracts/Validation/StructValidator.php index d8f65f7959..dfbe870060 100644 --- a/src/contracts/Validation/StructValidator.php +++ b/src/contracts/Validation/StructValidator.php @@ -47,15 +47,17 @@ public function validate($value, $constraints = null, $groups = null): Constrain $prefix = ltrim($value->getStructName(), '$') . '.'; foreach ($result as $error) { $path = $error->getPropertyPath(); + $root = $error->getRoot(); if (str_starts_with($path, $prefix)) { $path = substr($path, strlen($prefix)); + $root = $value->getStruct(); } $unwrappedError = new ConstraintViolation( $error->getMessage(), $error->getMessageTemplate(), $error->getParameters(), - $error->getRoot(), + $root, $path, $error->getInvalidValue(), $error->getPlural(), diff --git a/src/contracts/Validation/ValidatorStructWrapperInterface.php b/src/contracts/Validation/ValidatorStructWrapperInterface.php index 9e02fff664..9b8f062dac 100644 --- a/src/contracts/Validation/ValidatorStructWrapperInterface.php +++ b/src/contracts/Validation/ValidatorStructWrapperInterface.php @@ -6,7 +6,15 @@ */ namespace Ibexa\Contracts\Core\Validation; +/** + * @template T of object + */ interface ValidatorStructWrapperInterface { public function getStructName(): string; + + /** + * @phpstan-return T + */ + public function getStruct(): object; } diff --git a/tests/lib/Validation/StructValidatorTest.php b/tests/lib/Validation/StructValidatorTest.php index 594b388307..148d9620e1 100644 --- a/tests/lib/Validation/StructValidatorTest.php +++ b/tests/lib/Validation/StructValidatorTest.php @@ -71,8 +71,8 @@ public function testAssertValidStructWithInvalidStruct(): void $error = $errors->get(0); self::assertSame($initialError, $error); - self::assertEquals('validation error', $error->getMessage()); - self::assertEquals('struct.property', $error->getPropertyPath()); + self::assertSame('validation error', $error->getMessage()); + self::assertSame('struct.property', $error->getPropertyPath()); } public function testAssertValidStructWithInvalidWrapperStruct(): void @@ -80,26 +80,32 @@ public function testAssertValidStructWithInvalidWrapperStruct(): void $initialError = $this->createExampleConstraintViolation(); $initialErrors = $this->createExampleConstraintViolationList($initialError); - $struct = $this->createMock(ValidatorStructWrapperInterface::class); - $struct->expects(self::once()) + $wrapper = $this->createMock(ValidatorStructWrapperInterface::class); + $wrapper->expects(self::once()) ->method('getStructName') ->willReturn('$struct'); + $struct = new stdClass(); + $wrapper->expects(self::once()) + ->method('getStruct') + ->willReturn($struct); + $this->validator ->method('validate') ->with( - $struct, + $wrapper, null, ['Default', 'group'] )->willReturn($initialErrors); - $errors = $this->structValidator->validate($struct, null, ['Default', 'group']); + $errors = $this->structValidator->validate($wrapper, null, ['Default', 'group']); self::assertNotSame($initialErrors, $errors); self::assertCount(1, $errors); $error = $errors->get(0); self::assertNotSame($error, $initialError); self::assertSame('validation error', $error->getMessage()); + self::assertSame($struct, $error->getRoot()); self::assertSame('property', $error->getPropertyPath()); }