Skip to content
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

[Messenger / Serializer] Conflict when serialize ErrorDetailsStamp #50926

Open
Amorfx opened this issue Jul 10, 2023 · 4 comments
Open

[Messenger / Serializer] Conflict when serialize ErrorDetailsStamp #50926

Amorfx opened this issue Jul 10, 2023 · 4 comments

Comments

@Amorfx
Copy link

Amorfx commented Jul 10, 2023

Symfony version(s) affected

6.2.5

Description

For reasons of compatibility with an external service, I had to develop a custom Transport serializer used by Symfony Messenger. Like the default Serializer i want to serialize all the stamps in Header.

But when an error occurs when it consume the message, I noticed a fatal error related to a warning in FlattenExceptionNormalizer : Warning: Undefined array key "message".

After debugging it would seem that when the serializer serializes data from a FlattenException class it would use ProblemNormalizer from Serializer component and not FlattenExceptionNormalizer from Messenger component. And so in the end the serialized data does not include the message and to denormalize it causes an error.

So I have to remove the ErrorDetailsStamp from the headers in the encode function for it to work.

How to reproduce

Create a custom Messenger serializer for transport :

declare(strict_types=1);


final class MessageSerializer implements SerializerInterface
{
    private const STAMP_HEADER_PREFIX = 'X-Message-Stamp-';

    public function __construct(
       // ...
        private readonly SymfonySerializer $serializer,
        // ...
        private array $context = []
    ) {
        $this->context = $context + [Serializer::MESSENGER_SERIALIZATION_CONTEXT => true];
    }

    /**
     * @param array{body: string, headers: array<string>, mixed} $encodedEnvelope
     *
     * @throws Exception
     */
    public function decode(array $encodedEnvelope): Envelope
    {
        $stamps = $this->decodeStamps($encodedEnvelope);
        $stamps[] = new SerializedMessageStamp($encodedEnvelope['body']);
// ...
        $command = $this->serializer->deserialize($encodedEnvelope['body'], $commandName, 'json');
//  ...
        return new Envelope($command, $stamps);
    }

    /**
     * Used only for retry
     *
     * @return array<string, mixed>
     *
     * @throws Exception
     */
    public function encode(Envelope $envelope): array
    {
        /** @var SerializedMessageStamp|null $serializedMessageStamp */
        $serializedMessageStamp = $envelope->last(SerializedMessageStamp::class);

        $envelope = $envelope
            ->withoutStampsOfType(NonSendableStampInterface::class);
            // ->withoutStampsOfType(ErrorDetailsStamp::class);

        return [
            'body' => $serializedMessageStamp->getSerializedMessage(),
            'headers' => [
                self::EVENT_NAME_KEY_HEADER => $eventNameStamp->getEventName(),
                'Content-Type' => 'application/json',
            ] + $this->encodeStamps($envelope),
        ];
    }

    /**
     * @param array{headers?: array<string>, mixed} $encodedEnvelope
     *
     * @return StampInterface[]
     */
    private function decodeStamps(array $encodedEnvelope): array
    {
        $stamps = [];
        if (!isset($encodedEnvelope['headers'])) {
            return $stamps;
        }

        foreach ($encodedEnvelope['headers'] as $name => $value) {
            if (!str_starts_with($name, self::STAMP_HEADER_PREFIX)) {
                continue;
            }

            $stamps[] = $this->serializer->deserialize($value, substr($name, strlen(self::STAMP_HEADER_PREFIX)) . '[]', 'json', $this->context);
        }
        if ($stamps) {
            $stamps = array_merge(...$stamps);
        }

        return $stamps;
    }

    /**
     * @return array<string, string>
     */
    private function encodeStamps(Envelope $envelope): array
    {
        if (!$allStamps = $envelope->all()) {
            return [];
        }

        $headers = [];
        foreach ($allStamps as $class => $stamps) {
            $headers[self::STAMP_HEADER_PREFIX . $class] = $this->serializer->serialize($stamps, 'json', $this->context);
        }

        return $headers;
    }
}

Possible Solution

No response

Additional Context

Capture d’écran 2023-07-10 à 11 42 39

@fuduley1
Copy link

We experience similar issue with ErrorDetailsStamp. We have custom serializer which is almost the same as Amorfx's one. I get slightly different exception, though:

Failed to denormalize attribute "dataRepresentation" value for class "Symfony\Component\ErrorHandler\Exception\FlattenException": Expected argument of type "Symfony\Component\VarDumper\Cloner\Data", "null" given at property path "dataRepresentation". 

As a temporary solution, i removed "ErrorDetailsStamp"

@Amorfx
Copy link
Author

Amorfx commented Aug 25, 2023

Consequently I do not have all the details of the exceptions in the rejected messages it is quite problematic.

Any update ?

@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?

@Amorfx
Copy link
Author

Amorfx commented Feb 28, 2024

Yes its still relevant.

@carsonbot carsonbot removed the Stalled label Feb 28, 2024
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

4 participants