From 8ef45667f1f7a8af14a525b3bff85ac2dcbd9cfc Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Thu, 2 Jul 2026 18:45:04 +0200 Subject: [PATCH] feat: add ContentType attribute for default message content type --- .../Attribute/Endpoint/ContentType.php | 28 ++++ .../EndpointHeadersInterceptor.php | 12 +- .../ContentTypeAttributeTest.php | 120 ++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 packages/Ecotone/src/Messaging/Attribute/Endpoint/ContentType.php create mode 100644 packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/ContentTypeAttributeTest.php diff --git a/packages/Ecotone/src/Messaging/Attribute/Endpoint/ContentType.php b/packages/Ecotone/src/Messaging/Attribute/Endpoint/ContentType.php new file mode 100644 index 000000000..0b7b1a699 --- /dev/null +++ b/packages/Ecotone/src/Messaging/Attribute/Endpoint/ContentType.php @@ -0,0 +1,28 @@ +replaceIfExists; + } +} diff --git a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php index 8e9f6da03..26723f2c6 100644 --- a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php +++ b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php @@ -7,6 +7,7 @@ use DateTimeImmutable; use DateTimeInterface; use Ecotone\Messaging\Attribute\Endpoint\AddHeader; +use Ecotone\Messaging\Attribute\Endpoint\ContentType; use Ecotone\Messaging\Attribute\Endpoint\Delayed; use Ecotone\Messaging\Attribute\Endpoint\Priority; use Ecotone\Messaging\Attribute\Endpoint\RemoveHeader; @@ -41,7 +42,7 @@ public function __construct(private ExpressionEvaluationService $expressionEvalu } - public function addMetadata(Message $message, ?AddHeader $addHeader, ?Delayed $delayed, ?Priority $priority, ?TimeToLive $timeToLive, ?RemoveHeader $removeHeader): array + public function addMetadata(Message $message, ?AddHeader $addHeader, ?Delayed $delayed, ?Priority $priority, ?TimeToLive $timeToLive, ?RemoveHeader $removeHeader, ?ContentType $contentType): array { $metadata = []; @@ -56,6 +57,15 @@ public function addMetadata(Message $message, ?AddHeader $addHeader, ?Delayed $d } } + $isContentTypeHeaderExists = $message->getHeaders()->containsKey(MessageHeaders::CONTENT_TYPE); + if ($contentType) { + if ($contentType->shouldReplaceExistingHeader() || ! $isContentTypeHeaderExists) { + $metadata[MessageHeaders::CONTENT_TYPE] = $contentType->getHeaderValue(); + } else { + $metadata[MessageHeaders::CONTENT_TYPE] = $message->getHeaders()->get(MessageHeaders::CONTENT_TYPE); + } + } + $isDeliveryDelayHeaderExists = $message->getHeaders()->containsKey(MessageHeaders::DELIVERY_DELAY); if ($delayed && ($delayed->shouldReplaceExistingHeader() || ! $isDeliveryDelayHeaderExists)) { $metadata[MessageHeaders::DELIVERY_DELAY] = $delayed->getHeaderValue(); diff --git a/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/ContentTypeAttributeTest.php b/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/ContentTypeAttributeTest.php new file mode 100644 index 000000000..d06bc011a --- /dev/null +++ b/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/ContentTypeAttributeTest.php @@ -0,0 +1,120 @@ +order = $order; + } + }; + + $ecotoneLite = EcotoneLite::bootstrapFlowTesting( + [get_class($orderService), JsonConverter::class], + [$orderService, new JsonConverter()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('async'), + ], + ); + + $ecotoneLite->sendMessageDirectToChannel( + 'order.place', + MessageBuilder::withPayload('{"product":"Book"}')->build(), + ); + $ecotoneLite->run('async', ExecutionPollingMetadata::createWithTestingSetup()); + + $this->assertSame(['product' => 'Book'], $orderService->order); + } + + public function test_keeping_content_type_from_message_when_already_defined(): void + { + $orderService = new class () { + public ?array $order = null; + + #[ContentType('application/json')] + #[Asynchronous('async')] + #[CommandHandler('order.place', endpointId: 'orderPlaceEndpoint')] + public function place(array $order): void + { + $this->order = $order; + } + }; + + $ecotoneLite = EcotoneLite::bootstrapFlowTesting( + [get_class($orderService), JsonConverter::class], + [$orderService, new JsonConverter()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('async'), + ], + ); + + $ecotoneLite->sendMessageDirectToChannel( + 'order.place', + MessageBuilder::withPayload(serialize(['product' => 'Chair'])) + ->setContentType(MediaType::createApplicationXPHPSerialized()) + ->build(), + ); + $ecotoneLite->run('async', ExecutionPollingMetadata::createWithTestingSetup()); + + $this->assertSame(['product' => 'Chair'], $orderService->order); + } + + public function test_replacing_content_type_from_message_when_marked_to_replace_existing_one(): void + { + $orderService = new class () { + public ?array $order = null; + + #[ContentType('application/json', replaceIfExists: true)] + #[Asynchronous('async')] + #[CommandHandler('order.place', endpointId: 'orderPlaceEndpoint')] + public function place(array $order): void + { + $this->order = $order; + } + }; + + $ecotoneLite = EcotoneLite::bootstrapFlowTesting( + [get_class($orderService), JsonConverter::class], + [$orderService, new JsonConverter()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('async'), + ], + ); + + $ecotoneLite->sendMessageDirectToChannel( + 'order.place', + MessageBuilder::withPayload('{"product":"Table"}') + ->setContentType(MediaType::createApplicationXPHPSerialized()) + ->build(), + ); + $ecotoneLite->run('async', ExecutionPollingMetadata::createWithTestingSetup()); + + $this->assertSame(['product' => 'Table'], $orderService->order); + } +}