Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Serializer][Validator] Using Serializer attributes during constraint violation serializing #56962

Closed
@AlexMinaev19

Description

@AlexMinaev19

Symfony version(s) affected

7.0

Description

This problem exists only if you're using MetadataAwareNameConverter (with attributes #[SerializedName] or #[SerializedPath]). The current implementation of ConstraintViolationListNormalizer does not respect such attributes and way of name converting.

Why it happens?

Let's take a look at ConstraintViolationListNormalizer::normalize() method. On the line 63 (https://github.com/symfony/symfony/blob/7.0/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php#L63), we try to normalize the property path according to current name converter. If we're using CamelCaseToSnakeCaseNameConverter everything is working fine, but with MetadataAwareNameConverter it does not.

Here we pass the following parameters to MetadataAwareNameConverter::normalize() method:

  • $violation->getPropertyPath() as reported property path
  • null as class
  • $format as format
  • $context as normalization context

The most important parameter for us here is class. Since we don't know the class MetadataAwareNameConverter::normalize() fallback to another strategy of normalization (line https://github.com/symfony/symfony/blob/7.0/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php#L47). If there is no fallback name converter available, we will return passed property name itself. This is what happens in our case (we did not configure any fallback).

How to reproduce

For example, we have the following request object

// imports 

readonly class UserRequest
{
    #[Assert\NotBlank]
    #[Assert\Length(max: 10)]
    #[SerializedName('first_name')]
    public string $firstName;

    public public function __construct(string $firstName)
    {
        $this->firstName = $firstName;
    }
}

Also, let's imagine we have the following controller

// imports

#[AsController]
class PinCodeController
{
    #[Route('/user', name: 'update', methods: ['POST'])]
    public function updateUser(
        #[CurrentUser] UserInterface $user,
        #[MapRequestPayload(acceptFormat: 'json')] UserRequest $request
    ): JsonResponse {
        // handle request and perform logic
    }
}

And we have the following configuration of the framework

framework:
    serializer:
        default_context:
            !php/const Symfony\Component\Serializer\Normalizer\DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS: true

services:
    get_set_method_normalizer:
    class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
    tags:
        - { name: 'serializer.normalizer', priority: -999 }
    arguments:
        $classMetadataFactory: '@serializer.mapping.class_metadata_factory'
        $nameConverter: '@serializer.name_converter.metadata_aware'

Finally, we want to send the following request

{
  "first_name": "supercalifragilisticexpialidocious"
}

As you can see, we will exceed the maximum length of the field.

What do we expect to receive as a response:

{
  "type": "https://symfony.com/errors/validation",
  "title": "Validation Failed",
  "status": 422,
  "detail": "first_name: This value is too long. It should have 10 characters or less.",
  "violations": [
    {
      "propertyPath": "first_name",
      "title": "This value is too long. It should have 10 characters or less.",
      "template": "This value is too long. It should have {{ limit }} characters or less.",
      "parameters": {
        "{{ value }}": "\"supercalifragilisticexpialidocious\"",
        "{{ limit }}": "10"
      },
      "type": "urn:uuid:d94b19cc-114f-4f44-9cc4-4138e80a87b9"
    }
  ]
}

But we will receive the following one:

{
  "type": "https://symfony.com/errors/validation",
  "title": "Validation Failed",
  "status": 422,
  "detail": "firstName: This value is too long. It should have 10 characters or less.",
  "violations": [
    {
      "propertyPath": "firstName",
      "title": "This value is too long. It should have 10 characters or less.",
      "template": "This value is too long. It should have {{ limit }} characters or less.",
      "parameters": {
        "{{ value }}": "\"supercalifragilisticexpialidocious\"",
        "{{ limit }}": "10"
      },
      "type": "urn:uuid:d94b19cc-114f-4f44-9cc4-4138e80a87b9"
    }
  ]
}

As you can see, the current implementation of ConstraintViolationListNormalizer does not respect MetadataAwareNameConverter correctly and fallback to the property name.

Possible Solution

The first idea that I got is to try to pass the class name of the original object to which the property belongs (root). But I'm not sure that it will cover all possible cases.

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions