diff --git a/baseline.xml b/baseline.xml index 258e72349..6f24145b1 100644 --- a/baseline.xml +++ b/baseline.xml @@ -28,6 +28,11 @@ Headers + + + + + $aggregate::metadata() diff --git a/src/EventBus/Serializer/DeserializeFailed.php b/src/EventBus/Serializer/DeserializeFailed.php index b34f0a917..0a7aea809 100644 --- a/src/EventBus/Serializer/DeserializeFailed.php +++ b/src/EventBus/Serializer/DeserializeFailed.php @@ -4,7 +4,6 @@ namespace Patchlevel\EventSourcing\EventBus\Serializer; -use Patchlevel\EventSourcing\EventBus\Message; use RuntimeException; use function get_debug_type; @@ -17,12 +16,11 @@ public static function decodeFailed(): self return new self('Error while decoding message'); } - public static function invalidMessage(mixed $value): self + public static function invalidData(mixed $value): self { return new self( sprintf( - 'Value should me an instance of %s, but is %s', - Message::class, + 'Invalid data: %s', get_debug_type($value), ), ); diff --git a/src/EventBus/Serializer/EventSerializerMessageSerializer.php b/src/EventBus/Serializer/EventSerializerMessageSerializer.php new file mode 100644 index 000000000..646c85307 --- /dev/null +++ b/src/EventBus/Serializer/EventSerializerMessageSerializer.php @@ -0,0 +1,71 @@ +eventSerializer->serialize($message->event()); + + return base64_encode( + serialize( + [ + 'serializedEvent' => $serializedEvent, + 'headers' => $message->headers(), + ], + ), + ); + } + + public function deserialize(string $content): Message + { + $decodedString = base64_decode($content, true); + + if (!is_string($decodedString)) { + throw DeserializeFailed::decodeFailed(); + } + + $data = unserialize( + $decodedString, + [ + 'allowed_classes' => [ + SerializedEvent::class, + DateTimeImmutable::class, + ], + ], + ); + + if ( + !is_array($data) + || !isset($data['serializedEvent'], $data['headers']) + || !$data['serializedEvent'] instanceof SerializedEvent + || !is_array($data['headers']) + ) { + throw DeserializeFailed::invalidData($data); + } + + $event = $this->eventSerializer->deserialize($data['serializedEvent']); + + return Message::createWithHeaders($event, $data['headers']); + } +} diff --git a/src/EventBus/Serializer/PhpNativeMessageSerializer.php b/src/EventBus/Serializer/PhpNativeMessageSerializer.php index 5c9663cdc..d2791d044 100644 --- a/src/EventBus/Serializer/PhpNativeMessageSerializer.php +++ b/src/EventBus/Serializer/PhpNativeMessageSerializer.php @@ -33,7 +33,7 @@ public function deserialize(string $content): Message ); if (!$message instanceof Message) { - throw DeserializeFailed::invalidMessage($message); + throw DeserializeFailed::invalidData($message); } return $message; diff --git a/tests/Unit/EventBus/Serializer/EventSerializerMessageSerializerTest.php b/tests/Unit/EventBus/Serializer/EventSerializerMessageSerializerTest.php new file mode 100644 index 000000000..6b34a521f --- /dev/null +++ b/tests/Unit/EventBus/Serializer/EventSerializerMessageSerializerTest.php @@ -0,0 +1,113 @@ +withRecordedOn(new DateTimeImmutable('2020-01-01T20:00:00.000000+0100')); + + $eventSerializer = $this->prophesize(EventSerializer::class); + $eventSerializer->serialize($event)->shouldBeCalledOnce()->willReturn(new SerializedEvent( + 'profile_visited', + '{id: foo}', + )); + + $serializer = new EventSerializerMessageSerializer( + $eventSerializer->reveal(), + ); + + $content = $serializer->serialize($message); + + self::assertEquals('YToyOntzOjE1OiJzZXJpYWxpemVkRXZlbnQiO086NTE6IlBhdGNobGV2ZWxcRXZlbnRTb3VyY2luZ1xTZXJpYWxpemVyXFNlcmlhbGl6ZWRFdmVudCI6Mjp7czo0OiJuYW1lIjtzOjE1OiJwcm9maWxlX3Zpc2l0ZWQiO3M6NzoicGF5bG9hZCI7czo5OiJ7aWQ6IGZvb30iO31zOjc6ImhlYWRlcnMiO2E6Mzp7czoxMDoicmVjb3JkZWRPbiI7TzoxNzoiRGF0ZVRpbWVJbW11dGFibGUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0wMS0wMSAyMDowMDowMC4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MTtzOjg6InRpbWV6b25lIjtzOjY6IiswMTowMCI7fXM6MTQ6Im5ld1N0cmVhbVN0YXJ0IjtiOjA7czo4OiJhcmNoaXZlZCI7YjowO319', $content); + } + + public function testDeserialize(): void + { + $event = new ProfileVisited( + ProfileId::fromString('foo'), + ); + + $eventSerializer = $this->prophesize(EventSerializer::class); + $eventSerializer->deserialize(new SerializedEvent( + 'profile_visited', + '{id: foo}', + ))->shouldBeCalledOnce()->willReturn($event); + + $serializer = new EventSerializerMessageSerializer( + $eventSerializer->reveal(), + ); + + $message = $serializer->deserialize('YToyOntzOjE1OiJzZXJpYWxpemVkRXZlbnQiO086NTE6IlBhdGNobGV2ZWxcRXZlbnRTb3VyY2luZ1xTZXJpYWxpemVyXFNlcmlhbGl6ZWRFdmVudCI6Mjp7czo0OiJuYW1lIjtzOjE1OiJwcm9maWxlX3Zpc2l0ZWQiO3M6NzoicGF5bG9hZCI7czo5OiJ7aWQ6IGZvb30iO31zOjc6ImhlYWRlcnMiO2E6Mzp7czoxMDoicmVjb3JkZWRPbiI7TzoxNzoiRGF0ZVRpbWVJbW11dGFibGUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyMC0wMS0wMSAyMDowMDowMC4wMDAwMDAiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MTtzOjg6InRpbWV6b25lIjtzOjY6IiswMTowMCI7fXM6MTQ6Im5ld1N0cmVhbVN0YXJ0IjtiOjA7czo4OiJhcmNoaXZlZCI7YjowO319'); + + self::assertEquals($event, $message->event()); + self::assertEquals([ + 'recordedOn' => new DateTimeImmutable('2020-01-01T20:00:00.000000+0100'), + 'newStreamStart' => false, + 'archived' => false, + ], $message->headers()); + } + + public function testDeserializeDecodeFailed(): void + { + $this->expectException(DeserializeFailed::class); + + $eventSerializer = $this->prophesize(EventSerializer::class); + $serializer = new EventSerializerMessageSerializer( + $eventSerializer->reveal(), + ); + + $serializer->deserialize('!@#%$^&*()'); + } + + public function testEquals(): void + { + $event = new ProfileVisited( + ProfileId::fromString('foo'), + ); + + $message = Message::create($event) + ->withRecordedOn(new DateTimeImmutable('2020-01-01T20:00:00.000000+0100')); + + $eventSerializer = $this->prophesize(EventSerializer::class); + $eventSerializer->serialize($event)->shouldBeCalledOnce()->willReturn(new SerializedEvent( + 'profile_visited', + '{id: foo}', + )); + $eventSerializer->deserialize(new SerializedEvent( + 'profile_visited', + '{id: foo}', + ))->shouldBeCalledOnce()->willReturn($event); + + $serializer = new EventSerializerMessageSerializer( + $eventSerializer->reveal(), + ); + + $content = $serializer->serialize($message); + $clonedMessage = $serializer->deserialize($content); + + self::assertEquals($message, $clonedMessage); + } +}