Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
AlexMinaev19 opened this issue May 22, 2024 · 5 comments

Comments

@AlexMinaev19
Copy link

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

@AlexMinaev19 AlexMinaev19 changed the title [Serializer][Validator] Using Serializer attributes during constraint violation serializing [Serializer][Validator] Using Serializer attributes during constraint violation serializing May 22, 2024
@OskarStark OskarStark changed the title [Serializer][Validator] Using Serializer attributes during constraint violation serializing [Serializer][Validator] Using Serializer attributes during constraint violation serializing May 23, 2024
@carsonbot
Copy link

Hey, thanks for your report!
There has not been a lot of activity here for a while. Is this bug still relevant? Have you managed to find a workaround?

@carsonbot
Copy link

Hello? This issue is about to be closed if nobody replies.

@carsonbot
Copy link

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

@AlexMinaev19
Copy link
Author

PR is still not merged and it makes sense to reopen issue

@alexandre-le-borgne
Copy link

@carsonbot Repoen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants