Skip to content

Commit

Permalink
feature #35545 [Serializer] Allow to include the severity in Constrai…
Browse files Browse the repository at this point in the history
…ntViolationList (dunglas)

This PR was squashed before being merged into the 5.1-dev branch.

Discussion
----------

[Serializer] Allow to include the severity in ConstraintViolationList

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | n/a
| License       | MIT
| Doc PR        | todo

The Validator component allow to attach severity and other data to a violation: https://symfony.com/doc/current/validation/severity.html
This feature allow to include all or some fields of this payload in serialized errors.

This feature is already supported in API Platform (https://api-platform.com/docs/core/validation/#error-levels-and-payload-serialization). Including this in Symfony will allow us to migrate from our own RFC7807 normalizer to the Symfony one.

Usage: see the test.

Commits
-------

be855a2 [Serializer] Allow to include the severity in ConstraintViolationList
  • Loading branch information
fabpot committed May 5, 2020
2 parents 5a2aef1 + be855a2 commit 3cb4056
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/Serializer/CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* added support for scalar values denormalization
* added support for `\stdClass` to `ObjectNormalizer`
* added the ability to ignore properties using metadata (e.g. `@Symfony\Component\Serializer\Annotation\Ignore`)
* added an option to serialize constraint violations payloads (e.g. severity)

5.0.0
-----
Expand Down
Expand Up @@ -28,6 +28,7 @@ class ConstraintViolationListNormalizer implements NormalizerInterface, Cacheabl
const STATUS = 'status';
const TITLE = 'title';
const TYPE = 'type';
const PAYLOAD_FIELDS = 'payload_fields';

private $defaultContext;
private $nameConverter;
Expand All @@ -43,6 +44,18 @@ public function __construct($defaultContext = [], NameConverterInterface $nameCo
*/
public function normalize($object, string $format = null, array $context = [])
{
if (\array_key_exists(self::PAYLOAD_FIELDS, $context)) {
$payloadFieldsToSerialize = $context[self::PAYLOAD_FIELDS];
} elseif (\array_key_exists(self::PAYLOAD_FIELDS, $this->defaultContext)) {
$payloadFieldsToSerialize = $this->defaultContext[self::PAYLOAD_FIELDS];
} else {
$payloadFieldsToSerialize = [];
}

if (\is_array($payloadFieldsToSerialize) && [] !== $payloadFieldsToSerialize) {
$payloadFieldsToSerialize = array_flip($payloadFieldsToSerialize);
}

$violations = [];
$messages = [];
foreach ($object as $violation) {
Expand All @@ -57,6 +70,17 @@ public function normalize($object, string $format = null, array $context = [])
$violationEntry['type'] = sprintf('urn:uuid:%s', $code);
}

$constraint = $violation->getConstraint();
if (
[] !== $payloadFieldsToSerialize &&
$constraint &&
$constraint->payload &&
// If some or all payload fields are whitelisted, add them
$payloadFields = null === $payloadFieldsToSerialize || true === $payloadFieldsToSerialize ? $constraint->payload : array_intersect_key($constraint->payload, $payloadFieldsToSerialize)
) {
$violationEntry['payload'] = $payloadFields;
}

$violations[] = $violationEntry;

$prefix = $propertyPath ? sprintf('%s: ', $propertyPath) : '';
Expand Down
Expand Up @@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;

Expand Down Expand Up @@ -106,4 +107,32 @@ public function testNormalizeWithNameConverter()

$this->assertEquals($expected, $normalizer->normalize($list));
}

/**
* @dataProvider payloadFieldsProvider
*/
public function testNormalizePayloadFields($fields, array $expected = null)
{
$constraint = new NotNull();
$constraint->payload = ['severity' => 'warning', 'anotherField2' => 'aValue'];
$list = new ConstraintViolationList([
new ConstraintViolation('a', 'b', [], 'c', 'd', 'e', null, null, $constraint),
]);

$violation = $this->normalizer->normalize($list, null, [ConstraintViolationListNormalizer::PAYLOAD_FIELDS => $fields])['violations'][0];
if ([] === $fields) {
$this->assertArrayNotHasKey('payload', $violation);

return;
}
$this->assertSame($expected, $violation['payload']);
}

public function payloadFieldsProvider(): iterable
{
yield [['severity', 'anotherField1'], ['severity' => 'warning']];
yield [null, ['severity' => 'warning', 'anotherField2' => 'aValue']];
yield [true, ['severity' => 'warning', 'anotherField2' => 'aValue']];
yield [[]];
}
}

0 comments on commit 3cb4056

Please sign in to comment.