Skip to content

Commit

Permalink
remove nested encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidBadura committed Apr 9, 2024
1 parent 44d2a2d commit 757a04c
Show file tree
Hide file tree
Showing 16 changed files with 116 additions and 211 deletions.
5 changes: 5 additions & 0 deletions baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<code><![CDATA[$method->getName()]]></code>
</MixedMethodCall>
</file>
<file src="src/Message/Serializer/DefaultHeadersSerializer.php">
<MixedArgumentTypeCoercion>
<code><![CDATA[$headerPayload]]></code>
</MixedArgumentTypeCoercion>
</file>
<file src="src/Metadata/AggregateRoot/AggregateRootMetadataAwareMetadataFactory.php">
<InvalidReturnStatement>
<code><![CDATA[$aggregate::metadata()]]></code>
Expand Down
53 changes: 37 additions & 16 deletions src/Console/OutputStyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@

namespace Patchlevel\EventSourcing\Console;

use Patchlevel\EventSourcing\Aggregate\AggregateHeader;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Message\Serializer\HeadersSerializer;
use Patchlevel\EventSourcing\Serializer\Encoder\Encoder;
use Patchlevel\EventSourcing\Serializer\EventSerializer;
use Patchlevel\EventSourcing\Store\ArchivedHeader;
use Patchlevel\EventSourcing\Store\StreamStartHeader;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;

use function array_keys;
use function array_filter;
use function array_values;
use function sprintf;

Expand Down Expand Up @@ -42,25 +45,43 @@ public function message(
return;
}

try {
$headers = $headersSerializer->serialize($message->headers(), [Encoder::OPTION_PRETTY_PRINT => true]);
} catch (Throwable $error) {
$this->error(
sprintf(
'Error while serializing headers: %s',
$error->getMessage(),
),
);
$customHeaders = array_filter(
$message->headers(),
static fn ($header) => !$header instanceof AggregateHeader

Check failure on line 50 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Deptrac (locked, 8.3, ubuntu-latest)

Patchlevel\EventSourcing\Console\OutputStyle must not depend on Patchlevel\EventSourcing\Aggregate\AggregateHeader (Console on Aggregate)
&& !$header instanceof ArchivedHeader
&& !$header instanceof StreamStartHeader,
);

if ($this->isVeryVerbose()) {
$this->throwable($error);
}
$aggregateHeader = $message->header(AggregateHeader::class);

Check failure on line 55 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Deptrac (locked, 8.3, ubuntu-latest)

Patchlevel\EventSourcing\Console\OutputStyle must not depend on Patchlevel\EventSourcing\Aggregate\AggregateHeader (Console on Aggregate)
$streamStart = $message->hasHeader(StreamStartHeader::class);
$achieved = $message->hasHeader(ArchivedHeader::class);

return;
$this->title($data->name);
$this->horizontalTable(
[

Check warning on line 61 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.3, ubuntu-latest)

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ $streamStart = $message->hasHeader(StreamStartHeader::class); $achieved = $message->hasHeader(ArchivedHeader::class); $this->title($data->name); - $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); + $this->horizontalTable(['aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); if ($customHeaders !== []) { $this->block($headersSerializer->serialize(array_values($customHeaders))); }
'aggregateName',
'aggregateId',
'playhead',
'recordedOn',
'streamStart',
'archived',
],
[

Check warning on line 69 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.3, ubuntu-latest)

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ $streamStart = $message->hasHeader(StreamStartHeader::class); $achieved = $message->hasHeader(ArchivedHeader::class); $this->title($data->name); - $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); + $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], []); if ($customHeaders !== []) { $this->block($headersSerializer->serialize(array_values($customHeaders))); }
[

Check warning on line 70 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.3, ubuntu-latest)

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ $streamStart = $message->hasHeader(StreamStartHeader::class); $achieved = $message->hasHeader(ArchivedHeader::class); $this->title($data->name); - $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); + $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); if ($customHeaders !== []) { $this->block($headersSerializer->serialize(array_values($customHeaders))); }
$aggregateHeader->aggregateName,
$aggregateHeader->aggregateId,
$aggregateHeader->playhead,
$aggregateHeader->recordedOn->format('Y-m-d H:i:s'),
$streamStart ? 'yes' : 'no',

Check warning on line 75 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.3, ubuntu-latest)

Escaped Mutant for Mutator "Ternary": --- Original +++ New @@ @@ $streamStart = $message->hasHeader(StreamStartHeader::class); $achieved = $message->hasHeader(ArchivedHeader::class); $this->title($data->name); - $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); + $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'no' : 'yes', $achieved ? 'yes' : 'no']]); if ($customHeaders !== []) { $this->block($headersSerializer->serialize(array_values($customHeaders))); }
$achieved ? 'yes' : 'no',

Check warning on line 76 in src/Console/OutputStyle.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.3, ubuntu-latest)

Escaped Mutant for Mutator "Ternary": --- Original +++ New @@ @@ $streamStart = $message->hasHeader(StreamStartHeader::class); $achieved = $message->hasHeader(ArchivedHeader::class); $this->title($data->name); - $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'yes' : 'no']]); + $this->horizontalTable(['aggregateName', 'aggregateId', 'playhead', 'recordedOn', 'streamStart', 'archived'], [[$aggregateHeader->aggregateName, $aggregateHeader->aggregateId, $aggregateHeader->playhead, $aggregateHeader->recordedOn->format('Y-m-d H:i:s'), $streamStart ? 'yes' : 'no', $achieved ? 'no' : 'yes']]); if ($customHeaders !== []) { $this->block($headersSerializer->serialize(array_values($customHeaders))); }
],
],
);

if ($customHeaders !== []) {
$this->block($headersSerializer->serialize(array_values($customHeaders)));
}

$this->title($data->name);
$this->horizontalTable(array_keys($headers), [array_values($headers)]);
$this->block($data->payload);
}

Expand Down
25 changes: 17 additions & 8 deletions src/Message/Serializer/DefaultHeadersSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use Patchlevel\Hydrator\Hydrator;
use Patchlevel\Hydrator\MetadataHydrator;

use function is_array;

final class DefaultHeadersSerializer implements HeadersSerializer
{
public function __construct(
Expand All @@ -23,29 +25,36 @@ public function __construct(
/**
* @param list<object> $headers
* @param array<string, mixed> $options
*
* @return array<string, string>
*/
public function serialize(array $headers, array $options = []): array
public function serialize(array $headers, array $options = []): string
{
$serializedHeaders = [];
foreach ($headers as $header) {
$serializedHeaders[$this->messageHeaderRegistry->headerName($header::class)] = $this->encoder->encode($this->hydrator->extract($header), $options);
$serializedHeaders[$this->messageHeaderRegistry->headerName($header::class)] = $this->hydrator->extract($header);
}

return $serializedHeaders;
return $this->encoder->encode($serializedHeaders, $options);
}

/**
* @param array<string, string> $serializedHeaders
* @param array<string, mixed> $options
*
* @return list<object>
*/
public function deserialize(array $serializedHeaders): array
public function deserialize(string $string, array $options = []): array
{
$serializedHeaders = $this->encoder->decode($string, $options);

$headers = [];
foreach ($serializedHeaders as $headerName => $headerPayload) {
$headers[] = $this->hydrator->hydrate($this->messageHeaderRegistry->headerClass($headerName), $this->encoder->decode($headerPayload));
if (!is_array($headerPayload)) {
throw new InvalidArgument('header payload must be an array');
}

$headers[] = $this->hydrator->hydrate(
$this->messageHeaderRegistry->headerClass($headerName),
$headerPayload,
);
}

return $headers;
Expand Down
28 changes: 0 additions & 28 deletions src/Message/Serializer/DeserializeFailed.php

This file was deleted.

8 changes: 3 additions & 5 deletions src/Message/Serializer/HeadersSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ interface HeadersSerializer
/**
* @param list<object> $headers
* @param array<string, mixed> $options
*
* @return array<string, string>
*/
public function serialize(array $headers, array $options = []): array;
public function serialize(array $headers, array $options = []): string;

/**
* @param array<string, string> $serializedHeaders
* @param array<string, mixed> $options
*
* @return list<object>
*/
public function deserialize(array $serializedHeaders): array;
public function deserialize(string $string, array $options = []): array;
}
11 changes: 11 additions & 0 deletions src/Message/Serializer/InvalidArgument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Message\Serializer;

use RuntimeException;

final class InvalidArgument extends RuntimeException
{
}
2 changes: 0 additions & 2 deletions src/Store/DoctrineDbalStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ public function save(Message ...$messages): void
$this->connection->transactional(
function (Connection $connection) use ($messages): void {
$booleanType = Type::getType(Types::BOOLEAN);
$jsonType = Type::getType(Types::JSON);
$dateTimeType = Type::getType(Types::DATETIMETZ_IMMUTABLE);

$columns = [
Expand Down Expand Up @@ -194,7 +193,6 @@ function (Connection $connection) use ($messages): void {
$types[$offset + 7] = $booleanType;

$parameters[] = $this->headersSerializer->serialize($this->getCustomHeaders($message));
$types[$offset + 8] = $jsonType;

$position++;

Expand Down
2 changes: 1 addition & 1 deletion src/Store/DoctrineDbalStoreStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private function buildGenerator(
$message = $message->withHeader(new StreamStartHeader());
}

$customHeaders = $headersSerializer->deserialize(DoctrineHelper::normalizeCustomHeaders($data['custom_headers'], $platform));
$customHeaders = $headersSerializer->deserialize($data['custom_headers']);

yield $message->withHeaders($customHeaders);
}
Expand Down
13 changes: 0 additions & 13 deletions src/Store/DoctrineHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;

use function is_array;
use function is_int;

final class DoctrineHelper
Expand All @@ -36,16 +35,4 @@ public static function normalizePlayhead(string|int $playhead, AbstractPlatform

return $normalizedPlayhead;
}

/** @return array<string, string> */
public static function normalizeCustomHeaders(string $customHeaders, AbstractPlatform $platform): array
{
$normalizedCustomHeaders = Type::getType(Types::JSON)->convertToPHPValue($customHeaders, $platform);

if (!is_array($normalizedCustomHeaders)) {
throw new InvalidType('custom_headers', 'array');
}

return $normalizedCustomHeaders;
}
}
9 changes: 3 additions & 6 deletions tests/Unit/Console/Command/ShowAggregateCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ public function testSuccessful(): void
);

$headersSerializer = $this->prophesize(HeadersSerializer::class);
$headersSerializer->serialize($message->headers(), [Encoder::OPTION_PRETTY_PRINT => true])->willReturn(
['aggregate' => '{"aggregateName":"profile","aggregateId":"1","playhead":1,"recordedOn":"2020-01-01T20:00:00+01:00"}'],
$headersSerializer->serialize($message->headers())->willReturn(
['aggregate' => ['aggregateName' => 'profile', 'aggregateId' => '1', 'playhead' => 1, 'recordedOn' => '2020-01-01T20:00:00+01:00']],
);

$command = new ShowAggregateCommand(
Expand Down Expand Up @@ -236,9 +236,6 @@ public function testInteractiveSuccessful(): void
);

$headersSerializer = $this->prophesize(HeadersSerializer::class);
$headersSerializer->serialize($message->headers(), [Encoder::OPTION_PRETTY_PRINT => true])->willReturn(
['aggregate' => '{"aggregateName":"profile","aggregateId":"1","playhead":1,"recordedOn":"2020-01-01T20:00:00+01:00"}'],
);

$commandTest = new CommandTester(
new ShowAggregateCommand(
Expand All @@ -258,6 +255,6 @@ public function testInteractiveSuccessful(): void
self::assertStringContainsString('Enter the aggregate id', $display);
self::assertStringContainsString('"visitorId": "1"', $display);
self::assertStringContainsString('aggregate', $display);
self::assertStringContainsString('{"aggregateName":"profile","aggregateId":"1","playhead":1,"recordedOn":"2020-01-01T20:00:00+01:00"}', $display);
self::assertStringContainsString('profile', $display);
}
}
62 changes: 17 additions & 45 deletions tests/Unit/Console/OutputStyleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
use Patchlevel\EventSourcing\Serializer\EventSerializer;
use Patchlevel\EventSourcing\Serializer\SerializedEvent;
use Patchlevel\EventSourcing\Tests\Unit\Fixture\Email;
use Patchlevel\EventSourcing\Tests\Unit\Fixture\Header\FooHeader;
use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileCreated;
use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileId;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use RuntimeException;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;

Expand Down Expand Up @@ -46,9 +47,8 @@ public function testMessage(): void
));

$headersSerializer = $this->prophesize(HeadersSerializer::class);
$headersSerializer->serialize($message->headers(), [Encoder::OPTION_PRETTY_PRINT => true])->willReturn(
['aggregate' => '{"aggregateName":"profile","aggregateId":"1","playhead":1,"recordedOn":"2020-01-01T20:00:00+01:00"}'],
);
$headersSerializer->serialize(Argument::any())->shouldNotBeCalled();

$console = new OutputStyle($input, $output);

$console->message(
Expand All @@ -63,10 +63,10 @@ public function testMessage(): void
self::assertStringContainsString('profile', $content);
self::assertStringContainsString('{"id":"1","email":"[email protected]"}', $content);
self::assertStringContainsString('aggregate', $content);
self::assertStringContainsString('{"aggregateName":"profile","aggregateId":"1","playhead":1,"recordedOn":"2020-01-01T20:00:00+01:00"}', $content);
self::assertStringContainsString('profile', $content);
}

public function testMessageWithErrorAtEventSerialization(): void
public function testMessageWithCustomHeaders(): void
{
$input = new ArrayInput([]);
$output = new BufferedOutput();
Expand All @@ -76,44 +76,11 @@ public function testMessageWithErrorAtEventSerialization(): void
Email::fromString('[email protected]'),
);

$message = Message::create($event)
->withHeader(new AggregateHeader('profile', '1', 1, new DateTimeImmutable()));

$eventSerializer = $this->prophesize(EventSerializer::class);
$eventSerializer
->serialize($event, [Encoder::OPTION_PRETTY_PRINT => true])
->willThrow(new RuntimeException('Unknown Error'));

$headersSerializer = $this->prophesize(HeadersSerializer::class);
$headersSerializer->serialize($message->headers(), [Encoder::OPTION_PRETTY_PRINT => true])
->shouldNotBeCalled();

$console = new OutputStyle($input, $output);

$console->message(
$eventSerializer->reveal(),
$headersSerializer->reveal(),
$message,
);

$content = $output->fetch();

self::assertStringContainsString('Unknown Error', $content);
self::assertStringContainsString(ProfileCreated::class, $content);
}

public function testMessageWithErrorAtHeadersSerialization(): void
{
$input = new ArrayInput([]);
$output = new BufferedOutput();

$event = new ProfileCreated(
ProfileId::fromString('1'),
Email::fromString('[email protected]'),
);
$fooHeader = new FooHeader('foo');

$message = Message::create($event)
->withHeader(new AggregateHeader('profile', '1', 1, new DateTimeImmutable()));
->withHeader(new AggregateHeader('profile', '1', 1, new DateTimeImmutable()))
->withHeader($fooHeader);

$eventSerializer = $this->prophesize(EventSerializer::class);
$eventSerializer->serialize($event, [Encoder::OPTION_PRETTY_PRINT => true])->willReturn(new SerializedEvent(
Expand All @@ -122,8 +89,9 @@ public function testMessageWithErrorAtHeadersSerialization(): void
));

$headersSerializer = $this->prophesize(HeadersSerializer::class);
$headersSerializer->serialize($message->headers(), [Encoder::OPTION_PRETTY_PRINT => true])
->willThrow(new RuntimeException('Unknown Error'));
$headersSerializer->serialize([$fooHeader])->willReturn(
'{"aggregate":{"aggregateName":"profile","aggregateId":"1","playhead":1,"recordedOn":"2020-01-01T20:00:00+01:00"},"archived":[]}',
)->shouldBeCalled();

$console = new OutputStyle($input, $output);

Expand All @@ -135,6 +103,10 @@ public function testMessageWithErrorAtHeadersSerialization(): void

$content = $output->fetch();

self::assertStringContainsString('Unknown Error', $content);
self::assertStringContainsString('profile.created', $content);
self::assertStringContainsString('profile', $content);
self::assertStringContainsString('{"id":"1","email":"[email protected]"}', $content);
self::assertStringContainsString('aggregate', $content);
self::assertStringContainsString('profile', $content);
}
}
Loading

0 comments on commit 757a04c

Please sign in to comment.