From 187848dcf360f46b4c44b1b1665c16ee36243abe Mon Sep 17 00:00:00 2001 From: jlabedo <1191198+jlabedo@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:27:12 +0200 Subject: [PATCH 1/3] allow static function for converters --- .../ModuleConfiguration/ConverterModule.php | 49 ++++++++++++------ .../Conversion/ReferenceServiceConverter.php | 41 +-------------- .../ReferenceServiceConverterBuilder.php | 51 ------------------- .../Conversion/StaticCallConverter.php | 36 +++++++++++++ .../src/Messaging/Handler/InterfaceToCall.php | 2 +- .../ReferenceServiceConverterBuilderTest.php | 47 +++++++++++++++-- .../JMSConverterConfigurationModule.php | 3 +- .../JmsConverter/src/JMSHandlerAdapter.php | 25 ++++----- .../src/JMSHandlerAdapterBuilder.php | 2 +- 9 files changed, 128 insertions(+), 128 deletions(-) delete mode 100644 packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverterBuilder.php create mode 100644 packages/Ecotone/src/Messaging/Conversion/StaticCallConverter.php diff --git a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php index 3f7c84de6..cd2e26863 100644 --- a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php +++ b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php @@ -11,12 +11,16 @@ use Ecotone\Messaging\Config\Annotation\AnnotatedDefinitionReference; use Ecotone\Messaging\Config\Annotation\AnnotationModule; use Ecotone\Messaging\Config\Configuration; +use Ecotone\Messaging\Config\Container\CompilableBuilder; +use Ecotone\Messaging\Config\Container\Definition; +use Ecotone\Messaging\Config\Container\Reference; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ModuleReferenceSearchService; -use Ecotone\Messaging\Conversion\ConverterBuilder; use Ecotone\Messaging\Conversion\ConverterReferenceBuilder; -use Ecotone\Messaging\Conversion\ReferenceServiceConverterBuilder; +use Ecotone\Messaging\Conversion\ReferenceServiceConverter; +use Ecotone\Messaging\Conversion\StaticCallConverter; use Ecotone\Messaging\Handler\InterfaceToCallRegistry; +use Ecotone\Messaging\Support\InvalidArgumentException; #[ModuleAnnotation] /** @@ -24,19 +28,13 @@ */ class ConverterModule extends NoExternalConfigurationModule implements AnnotationModule { - /** - * @var ConverterBuilder[] - */ - private array $converterBuilders = []; - /** * ConverterModule constructor. * - * @param array $converterBuilders + * @param CompilableBuilder[] $converterBuilders */ - private function __construct(array $converterBuilders) + private function __construct(private array $converterBuilders) { - $this->converterBuilders = $converterBuilders; } /** @@ -50,12 +48,31 @@ public static function create(AnnotationFinder $annotationRegistrationService, I foreach ($registrations as $registration) { $interfaceToCall = $interfaceToCallRegistry->getFor($registration->getClassName(), $registration->getMethodName()); - $converterBuilders[] = ReferenceServiceConverterBuilder::create( - AnnotatedDefinitionReference::getReferenceFor($registration), - $registration->getMethodName(), - $interfaceToCall->getFirstParameter()->getTypeDescriptor(), - $interfaceToCall->getReturnType() - ); + + if (!$interfaceToCall->hasSingleParameter()) { + throw InvalidArgumentException::create("Converter should have only single parameter: {$interfaceToCall}"); + } + if ($interfaceToCall->getReturnType()->isVoid() || $interfaceToCall->getReturnType()->isUnionType()) { + throw InvalidArgumentException::create("Converter cannot have void return type: {$interfaceToCall}"); + } + if ($interfaceToCall->getReturnType()->isUnionType()) { + throw InvalidArgumentException::create("Converter cannot have union type as parameter: {$interfaceToCall}"); + } + if ($interfaceToCall->isStaticallyCalled()) { + $converterBuilders[] = new Definition(StaticCallConverter::class, [ + $interfaceToCall->getInterfaceName(), + $interfaceToCall->getMethodName(), + $interfaceToCall->getFirstParameter()->getTypeDescriptor(), + $interfaceToCall->getReturnType(), + ]); + } else { + $converterBuilders[] = new Definition(ReferenceServiceConverter::class, [ + new Reference(AnnotatedDefinitionReference::getReferenceFor($registration)), + $registration->getMethodName(), + $interfaceToCall->getFirstParameter()->getTypeDescriptor(), + $interfaceToCall->getReturnType(), + ]); + } } $registrations = $annotationRegistrationService->findAnnotatedClasses(MediaTypeConverter::class); diff --git a/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverter.php b/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverter.php index c38fccbe6..8e1bb77b7 100644 --- a/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverter.php +++ b/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverter.php @@ -19,45 +19,8 @@ */ class ReferenceServiceConverter implements Converter { - private object $object; - private string $method; - private Type $sourceType; - private Type $targetType; - - /** - * ReferenceConverter constructor. - * @param object $object - * @param string $method - * @param Type $sourceType - * @param Type $targetType - * @throws \Ecotone\Messaging\MessagingException - */ - public function __construct($object, string $method, Type $sourceType, Type $targetType) - { - Assert::isObject($object, ''); - $this->object = $object; - $this->method = $method; - $this->sourceType = $sourceType; - $this->targetType = $targetType; - - $reflectionMethod = new ReflectionMethod($object, $method); - - if (count($reflectionMethod->getParameters()) !== 1) { - throw InvalidArgumentException::create("Converter should have only single parameter: {$reflectionMethod}"); - } - } - - /** - * @param $object - * @param string $method - * @param Type $sourceType - * @param Type $targetType - * @return ReferenceServiceConverter - * @throws \Ecotone\Messaging\MessagingException - */ - public static function create($object, string $method, Type $sourceType, Type $targetType): self + public function __construct(private object $object, private string $method, private Type $sourceType, private Type $targetType) { - return new self($object, $method, $sourceType, $targetType); } /** @@ -65,7 +28,7 @@ public static function create($object, string $method, Type $sourceType, Type $t */ public function convert($source, Type $sourceType, MediaType $sourceMediaType, Type $targetType, MediaType $targetMediaType) { - return call_user_func([$this->object, $this->method], $source); + return $this->object->{$this->method}($source); } /** diff --git a/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverterBuilder.php b/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverterBuilder.php deleted file mode 100644 index 725538af4..000000000 --- a/packages/Ecotone/src/Messaging/Conversion/ReferenceServiceConverterBuilder.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ -/** - * licence Apache-2.0 - */ -class ReferenceServiceConverterBuilder implements CompilableBuilder -{ - private function __construct(private string $referenceName, private string $methodName, private Type $sourceType, private Type $targetType) - { - Assert::isFalse($sourceType->isUnionType(), "Source type for converter cannot be union type, {$sourceType} given for {$referenceName}:{$methodName}."); - Assert::isFalse($targetType->isUnionType(), "Source type for converter cannot be union type, {$targetType} given for {$referenceName}:{$methodName}."); - } - - /** - * @param string $referenceName - * @param string $method - * @param Type $sourceType - * @param Type $targetType - * @return ReferenceServiceConverterBuilder - */ - public static function create(string $referenceName, string $method, Type $sourceType, Type $targetType): self - { - return new self($referenceName, $method, $sourceType, $targetType); - } - - public function compile(MessagingContainerBuilder $builder): Definition - { - return new Definition(ReferenceServiceConverter::class, [ - new Reference($this->referenceName), - $this->methodName, - $this->sourceType, - $this->targetType, - ]); - } -} diff --git a/packages/Ecotone/src/Messaging/Conversion/StaticCallConverter.php b/packages/Ecotone/src/Messaging/Conversion/StaticCallConverter.php new file mode 100644 index 000000000..197279f8f --- /dev/null +++ b/packages/Ecotone/src/Messaging/Conversion/StaticCallConverter.php @@ -0,0 +1,36 @@ +classname::{$this->method}($source); + } + + /** + * @inheritDoc + */ + public function matches(Type $sourceType, MediaType $sourceMediaType, Type $targetType, MediaType $targetMediaType): bool + { + return $sourceMediaType->isCompatibleWithParsed(MediaType::APPLICATION_X_PHP) + && $targetMediaType->isCompatibleWithParsed(MediaType::APPLICATION_X_PHP) + && $sourceType->isCompatibleWith($this->sourceType) + && $targetType->acceptType($this->targetType); + } +} diff --git a/packages/Ecotone/src/Messaging/Handler/InterfaceToCall.php b/packages/Ecotone/src/Messaging/Handler/InterfaceToCall.php index 665c27267..d2cb7013d 100644 --- a/packages/Ecotone/src/Messaging/Handler/InterfaceToCall.php +++ b/packages/Ecotone/src/Messaging/Handler/InterfaceToCall.php @@ -218,7 +218,7 @@ public function getMethodAnnotationsOf(ObjectType|string $className): array return $methodAnnotations; } - public function isStaticallyCalled(): ?bool + public function isStaticallyCalled(): bool { return $this->isStaticallyCalled; } diff --git a/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php b/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php index fe205d2e0..b9a679e95 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php @@ -4,6 +4,9 @@ namespace Test\Ecotone\Messaging\Unit\Conversion; +use Ecotone\Lite\EcotoneLite; +use Ecotone\Messaging\Attribute\Converter; +use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Handler\ServiceActivator\ServiceActivatorBuilder; use Ecotone\Messaging\Support\InvalidArgumentException; use Ecotone\Test\ComponentTestBuilder; @@ -70,10 +73,8 @@ public function test_throwing_exception_if_there_is_more_parameters_than_one_in_ ->build(); } - public function test_throwing_exception_if_converter_containing_union_source_type() + public function test_not_throwing_exception_if_converter_containing_union_source_type() { - $this->expectException(InvalidArgumentException::class); - ComponentTestBuilder::create([ExampleIncorrectUnionSourceTypeConverterService::class]) ->withReference(ExampleIncorrectUnionSourceTypeConverterService::class, new ExampleIncorrectUnionSourceTypeConverterService()) ->withMessageHandler( @@ -82,6 +83,8 @@ public function test_throwing_exception_if_converter_containing_union_source_typ ->withPassThroughMessageOnVoidInterface(true) ) ->build(); + + $this->expectNotToPerformAssertions(); } public function test_throwing_exception_if_converter_containing_union_target_type() @@ -97,4 +100,42 @@ public function test_throwing_exception_if_converter_containing_union_target_typ ) ->build(); } + + public function test_static_converter(): void + { + $staticConverter = new class { + /** + * @param string[] $data + * @return stdClass[] + */ + #[Converter] + public static function convert(array $data): iterable + { + $converted = []; + foreach ($data as $str) { + $converted[] = new stdClass(); + } + + return $converted; + } + }; + + $ecotone = EcotoneLite::bootstrapFlowTesting( + [$staticConverter::class], + [$staticConverter], + configuration: ServiceConfiguration::createWithDefaults() + ->addExtensionObject( + ServiceActivatorBuilder::createWithDirectReference(ServiceExpectingOneArgument::create(), 'withArrayStdClasses') + ->withInputChannelName($inputChannel = 'inputChannel') + ) + ); + + $this->assertEquals( + [new stdClass()], + $ecotone->sendDirectToChannel( + $inputChannel, + ['some'] + ) + ); + } } diff --git a/packages/JmsConverter/src/Configuration/JMSConverterConfigurationModule.php b/packages/JmsConverter/src/Configuration/JMSConverterConfigurationModule.php index ce23f0729..1b19b219b 100644 --- a/packages/JmsConverter/src/Configuration/JMSConverterConfigurationModule.php +++ b/packages/JmsConverter/src/Configuration/JMSConverterConfigurationModule.php @@ -42,7 +42,6 @@ public static function create(AnnotationFinder $annotationRegistrationService, I $converters = []; foreach ($registrations as $registration) { - $reference = AnnotatedDefinitionReference::getReferenceFor($registration); $interfaceToCall = $interfaceToCallRegistry->getFor($registration->getClassName(), $registration->getMethodName()); $fromTypes = $interfaceToCall->getFirstParameter()->getTypeDescriptor(); @@ -63,7 +62,7 @@ public static function create(AnnotationFinder $annotationRegistrationService, I $converters[] = new JMSHandlerAdapterBuilder( $fromType, $toType, - Reference::to($reference), + $interfaceToCall->isStaticallyCalled() ? $interfaceToCall->getInterfaceName() : Reference::to(AnnotatedDefinitionReference::getReferenceFor($registration)), $registration->getMethodName(), ); } diff --git a/packages/JmsConverter/src/JMSHandlerAdapter.php b/packages/JmsConverter/src/JMSHandlerAdapter.php index 351a39336..438a66993 100644 --- a/packages/JmsConverter/src/JMSHandlerAdapter.php +++ b/packages/JmsConverter/src/JMSHandlerAdapter.php @@ -3,7 +3,6 @@ namespace Ecotone\JMSConverter; use Closure; -use Ecotone\Messaging\Config\Container\Definition; use Ecotone\Messaging\Handler\Type; use Ecotone\Messaging\Support\Assert; use JMS\Serializer\GraphNavigator; @@ -13,27 +12,23 @@ */ class JMSHandlerAdapter { - public function __construct(private Type $fromType, private Type $toType, private object $object, private string $methodName) + public function __construct(private Type $fromType, private Type $toType, private string|object $object, private string $methodName) { Assert::isTrue($fromType->isClassOrInterface() || $toType->isClassOrInterface(), 'At least one side of converter must be class'); Assert::isFalse($fromType->isClassOrInterface() && $toType->isClassOrInterface(), 'Both sides of converter cannot to be classes'); } - public static function create(Type $fromType, Type $toType, string $referenceName, string $methodName): self - { - return new self($fromType, $toType, $referenceName, $methodName); - } - - public static function createWithDefinition(Type $fromType, Type $toType, Definition $definition, string $methodName): self - { - return new self($fromType, $toType, $definition, $methodName); - } - public function getSerializerClosure(): Closure { - return function ($visitor, $data) { - return $this->object->{$this->methodName}($data); - }; + if (is_string($this->object)) { + return function ($visitor, $data) { + return $this->object::{$this->methodName}($data); + }; + } else { + return function ($visitor, $data) { + return $this->object->{$this->methodName}($data); + }; + } } public function getRelatedClass(): string diff --git a/packages/JmsConverter/src/JMSHandlerAdapterBuilder.php b/packages/JmsConverter/src/JMSHandlerAdapterBuilder.php index bc71fd220..d798c550f 100644 --- a/packages/JmsConverter/src/JMSHandlerAdapterBuilder.php +++ b/packages/JmsConverter/src/JMSHandlerAdapterBuilder.php @@ -16,7 +16,7 @@ class JMSHandlerAdapterBuilder implements CompilableBuilder public function __construct( private Type $fromType, private Type $toType, - private Definition|Reference $objectToCallOn, + private Definition|Reference|string $objectToCallOn, private string $methodName, ) { } From 451ce2fd52b1f0bf5ad4bdd556448cebc76e923c Mon Sep 17 00:00:00 2001 From: jlabedo <1191198+jlabedo@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:31:35 +0200 Subject: [PATCH 2/3] update projecting benchmark --- Monorepo/Benchmark/ProjectingBenchmark.php | 41 +++++-------------- .../Common/Event/PriceWasChanged.php | 17 ++++++++ .../Common/Event/ProductWasRegistered.php | 17 ++++++++ 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/Monorepo/Benchmark/ProjectingBenchmark.php b/Monorepo/Benchmark/ProjectingBenchmark.php index 3f0abdb09..840e1a8b0 100644 --- a/Monorepo/Benchmark/ProjectingBenchmark.php +++ b/Monorepo/Benchmark/ProjectingBenchmark.php @@ -8,6 +8,7 @@ use Ecotone\Messaging\Config\ConfiguredMessagingSystem; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; +use Ecotone\Modelling\CommandBus; use Ecotone\Projecting\ProjectionRegistry; use Ecotone\Test\LicenceTesting; use Enqueue\Dbal\DbalConnectionFactory; @@ -52,7 +53,6 @@ public static function bootEcotone(string $name, array $container, array $namesp ->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ ModulePackageList::EVENT_SOURCING_PACKAGE, ModulePackageList::DBAL_PACKAGE, - ModulePackageList::JMS_CONVERTER_PACKAGE ])), useCachedVersion: true, pathToRootCatalog: self::getProjectDir(), @@ -154,29 +154,8 @@ private static function executeWithDeletion(ConfiguredMessagingSystem $messaging ], $queryBus->sendWithRouting('product.getPriceChange', $productId), 'Price changes should be projected again after deletion'); } - public function fillEcotone(): void - { - $projection = self::$ecotone->getServiceFromContainer(ProjectionRegistry::class)->get(PriceChangeOverTimeProjectionWithEcotoneProjection::NAME); + public function fill(): void { $commandBus = self::$ecotone->getCommandBus(); - $projection->delete(); - - self::$expectedProductIds = []; - for ($i = 0; $i < 100; $i++) { - self::$expectedProductIds[] = $productId = Uuid::uuid4()->toString(); - $commandBus->send(new RegisterProduct($productId, 100)); - $commandBus->send(new ChangePrice($productId, 120)); - $commandBus->send(new ChangePrice($productId, 130)); - } - - $projection->delete(); - } - - public function fillProoph(): void - { - $projection = self::$prooph->getServiceFromContainer(ProjectionManager::class); - $commandBus = self::$prooph->getCommandBus(); - $projection->deleteProjection(PriceChangeOverTimeProjectionWithEcotoneProjection::NAME); - self::$expectedProductIds = []; for ($i = 0; $i < 100; $i++) { self::$expectedProductIds[] = $productId = Uuid::uuid4()->toString(); @@ -184,18 +163,17 @@ public function fillProoph(): void $commandBus->send(new ChangePrice($productId, 120)); $commandBus->send(new ChangePrice($productId, 130)); } - - $projection->deleteProjection(PriceChangeOverTimeProjectionWithEcotoneProjection::NAME); } - #[BeforeMethods(["setUp", "fillEcotone"])] - #[Revs(1), Warmup(0)] + #[BeforeMethods(["setUp", "fill"])] + #[Iterations(1), Warmup(0)] public function bench_ecotone_projection_backfill(): void { + $projectionManager = self::$ecotone->getServiceFromContainer(ProjectionRegistry::class)->get(PriceChangeOverTimeProjectionWithEcotoneProjection::NAME); + $projectionManager->delete(); Assert::assertEquals([], self::$ecotone->getQueryBus()->sendWithRouting('product.getPriceChange', self::$expectedProductIds[0]) ); - $projectionManager = self::$ecotone->getServiceFromContainer(ProjectionRegistry::class)->get(PriceChangeOverTimeProjectionWithEcotoneProjection::NAME); $projectionManager->backfill(); Assert::assertEquals([ new PriceChange(100, 0), @@ -206,14 +184,15 @@ public function bench_ecotone_projection_backfill(): void ); } - #[BeforeMethods(["setUp", "fillProoph"])] - #[Revs(1), Warmup(0)] + #[BeforeMethods(["setUp", "fill"])] + #[Iterations(1), Warmup(0)] public function bench_prooph_projection_backfill(): void { + $projectionManager = self::$prooph->getServiceFromContainer(ProjectionManager::class); + $projectionManager->deleteProjection(PriceChangeOverTimeProjection::NAME); Assert::assertEquals([], self::$prooph->getQueryBus()->sendWithRouting('product.getPriceChange', self::$expectedProductIds[0]) ); - $projectionManager = self::$prooph->getServiceFromContainer(ProjectionManager::class); $projectionManager->triggerProjection(PriceChangeOverTimeProjection::NAME); Assert::assertEquals([ new PriceChange(100, 0), diff --git a/Monorepo/ExampleAppEventSourcing/Common/Event/PriceWasChanged.php b/Monorepo/ExampleAppEventSourcing/Common/Event/PriceWasChanged.php index 5ab36aeba..96f0204a9 100644 --- a/Monorepo/ExampleAppEventSourcing/Common/Event/PriceWasChanged.php +++ b/Monorepo/ExampleAppEventSourcing/Common/Event/PriceWasChanged.php @@ -2,6 +2,8 @@ namespace Monorepo\ExampleAppEventSourcing\Common\Event; +use Ecotone\Messaging\Attribute\Converter; + class PriceWasChanged { public function __construct(private string $productId, private float $price) {} @@ -15,4 +17,19 @@ public function getPrice(): float { return $this->price; } + + #[Converter] + public static function fromArray(array $data): self + { + return new self($data['productId'], $data['price']); + } + + #[Converter] + public static function toArray(self $object): array + { + return [ + 'productId' => $object->productId, + 'price' => $object->price, + ]; + } } \ No newline at end of file diff --git a/Monorepo/ExampleAppEventSourcing/Common/Event/ProductWasRegistered.php b/Monorepo/ExampleAppEventSourcing/Common/Event/ProductWasRegistered.php index c1355d0e7..09d6a1d99 100644 --- a/Monorepo/ExampleAppEventSourcing/Common/Event/ProductWasRegistered.php +++ b/Monorepo/ExampleAppEventSourcing/Common/Event/ProductWasRegistered.php @@ -2,6 +2,8 @@ namespace Monorepo\ExampleAppEventSourcing\Common\Event; +use Ecotone\Messaging\Attribute\Converter; + class ProductWasRegistered { public function __construct(private string $productId, private float $price) {} @@ -15,4 +17,19 @@ public function getPrice(): float { return $this->price; } + + #[Converter] + public static function fromArray(array $data): self + { + return new self($data['productId'], $data['price']); + } + + #[Converter] + public static function toArray(self $object): array + { + return [ + 'productId' => $object->productId, + 'price' => $object->price, + ]; + } } \ No newline at end of file From 7c56abc02d009e462dd9bd87e32680ec269ad253 Mon Sep 17 00:00:00 2001 From: jlabedo <1191198+jlabedo@users.noreply.github.com> Date: Thu, 2 Oct 2025 17:31:57 +0200 Subject: [PATCH 3/3] add JMS test case for unions --- .../ModuleConfiguration/ConverterModule.php | 2 +- .../ReferenceServiceConverterBuilderTest.php | 15 +++++++++++- .../tests/Unit/JMSConverterTest.php | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php index cd2e26863..9bcfa94ae 100644 --- a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php +++ b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/ConverterModule.php @@ -52,7 +52,7 @@ public static function create(AnnotationFinder $annotationRegistrationService, I if (!$interfaceToCall->hasSingleParameter()) { throw InvalidArgumentException::create("Converter should have only single parameter: {$interfaceToCall}"); } - if ($interfaceToCall->getReturnType()->isVoid() || $interfaceToCall->getReturnType()->isUnionType()) { + if ($interfaceToCall->getReturnType()->isVoid()) { throw InvalidArgumentException::create("Converter cannot have void return type: {$interfaceToCall}"); } if ($interfaceToCall->getReturnType()->isUnionType()) { diff --git a/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php b/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php index b9a679e95..0f719ddeb 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Conversion/ReferenceServiceConverterBuilderTest.php @@ -73,7 +73,7 @@ public function test_throwing_exception_if_there_is_more_parameters_than_one_in_ ->build(); } - public function test_not_throwing_exception_if_converter_containing_union_source_type() + public function test_not_throwing_exception_if_converter_containing_source_union_source_type() { ComponentTestBuilder::create([ExampleIncorrectUnionSourceTypeConverterService::class]) ->withReference(ExampleIncorrectUnionSourceTypeConverterService::class, new ExampleIncorrectUnionSourceTypeConverterService()) @@ -87,6 +87,19 @@ public function test_not_throwing_exception_if_converter_containing_union_source $this->expectNotToPerformAssertions(); } + public function test_throwing_exception_if_converter_returning_union_source_type() + { + $this->expectException(InvalidArgumentException::class); + ComponentTestBuilder::create([ExampleIncorrectUnionReturnTypeConverterService::class]) + ->withReference(ExampleIncorrectUnionReturnTypeConverterService::class, new ExampleIncorrectUnionReturnTypeConverterService()) + ->withMessageHandler( + ServiceActivatorBuilder::createWithDirectReference(ServiceExpectingOneArgument::create(), 'withArrayStdClasses') + ->withInputChannelName($inputChannel = 'inputChannel') + ->withPassThroughMessageOnVoidInterface(true) + ) + ->build(); + } + public function test_throwing_exception_if_converter_containing_union_target_type() { $this->expectException(InvalidArgumentException::class); diff --git a/packages/JmsConverter/tests/Unit/JMSConverterTest.php b/packages/JmsConverter/tests/Unit/JMSConverterTest.php index 7d328a5d1..54f503bd6 100644 --- a/packages/JmsConverter/tests/Unit/JMSConverterTest.php +++ b/packages/JmsConverter/tests/Unit/JMSConverterTest.php @@ -8,6 +8,7 @@ use Ecotone\JMSConverter\JMSConverter; use Ecotone\JMSConverter\JMSConverterConfiguration; use Ecotone\Lite\EcotoneLite; +use Ecotone\Messaging\Attribute\Converter; use Ecotone\Messaging\Config\ModulePackageList; use Ecotone\Messaging\Config\ServiceConfiguration; use Ecotone\Messaging\Conversion\ConversionException; @@ -442,6 +443,28 @@ public function test_matching_conversion_from_array_to_object_and_opposite() ); } + public function test_it_works_with_union_converters(): void + { + $converter = new class { + #[Converter] + public function convert(Status|stdClass $status): string + { + return $status instanceof Status ? 'custom ' . $status->getType() : 'stdClass'; + } + }; + + $this->assertSame( + '{"status":"custom active"}' + ,$this->getJMSConverter([$converter])->convert( + new Person(new Status('active')), + Type::createFromVariable(new Person(new Status('active'))), + MediaType::createApplicationXPHP(), + Type::string(), + MediaType::createApplicationJson() + ) + ); + } + private function assertSerializationAndDeserializationWithJSON(object|array $toSerialize, string $expectedSerializationString, $jmsHandlerAdapters = [], ?JMSConverterConfiguration $configuration = null): void { $serialized = $this->serializeToJson($toSerialize, $jmsHandlerAdapters, $configuration);