diff --git a/src/DataConverter/EncodedValues.php b/src/DataConverter/EncodedValues.php index 0569c29bf..981583560 100644 --- a/src/DataConverter/EncodedValues.php +++ b/src/DataConverter/EncodedValues.php @@ -16,6 +16,7 @@ use React\Promise\PromiseInterface; use Temporal\Api\Common\V1\Payload; use Temporal\Api\Common\V1\Payloads; +use Temporal\Workflow\ReturnType; use Traversable; /** @@ -213,8 +214,11 @@ private function isVoidType(mixed $type = null): bool { return match (true) { $type === null => true, - $type instanceof Type => \in_array($type->getName(), [Type::TYPE_VOID, Type::TYPE_NULL], true), - $type instanceof \ReflectionNamedType => $type->getName() === Type::TYPE_VOID, + \is_string($type) => \in_array($type, [Type::TYPE_VOID, Type::TYPE_NULL, Type::TYPE_ANY], true), + $type instanceof Type => $type->allowsNull(), + $type instanceof ReturnType => $type->nullable, + $type instanceof \ReflectionNamedType => $type->getName() === Type::TYPE_VOID || $type->allowsNull(), + $type instanceof \ReflectionUnionType => $type->allowsNull(), default => false, }; } diff --git a/src/Workflow/ReturnType.php b/src/Workflow/ReturnType.php index 4433e18ab..6544e6675 100644 --- a/src/Workflow/ReturnType.php +++ b/src/Workflow/ReturnType.php @@ -52,6 +52,6 @@ public function __construct( bool $nullable = false ) { $this->name = $name; - $this->nullable = $nullable; + $this->nullable = $nullable || (new Type($name))->allowsNull(); } } diff --git a/tests/Unit/Protocol/EncodingTestCase.php b/tests/Unit/Protocol/EncodingTestCase.php index edb9c3ee5..18abb9dee 100644 --- a/tests/Unit/Protocol/EncodingTestCase.php +++ b/tests/Unit/Protocol/EncodingTestCase.php @@ -11,9 +11,12 @@ namespace Temporal\Tests\Unit\Protocol; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Temporal\DataConverter\DataConverter; use Temporal\DataConverter\EncodedValues; +use Temporal\DataConverter\Type; +use Temporal\Workflow\ReturnType; /** * @group unit @@ -27,4 +30,77 @@ public function nullValuesAreReturned(): void $encodedValues = EncodedValues::fromValues([null, 'something'], new DataConverter()); $this->assertNull($encodedValues->getValue(0)); } + + public static function getNotNullableTypes(): iterable + { + yield [Type::create(Type::TYPE_ARRAY)]; + yield [Type::create(Type::TYPE_OBJECT)]; + yield [Type::create(Type::TYPE_STRING)]; + yield [Type::create(Type::TYPE_BOOL)]; + yield [Type::create(Type::TYPE_INT)]; + yield [Type::create(Type::TYPE_FLOAT)]; + yield [Type::create(Type::TYPE_TRUE)]; + yield [Type::create(Type::TYPE_FALSE)]; + yield [Type::create(self::class)]; + yield [Type::TYPE_ARRAY]; + yield [Type::TYPE_OBJECT]; + yield [Type::TYPE_STRING]; + yield [Type::TYPE_BOOL]; + yield [Type::TYPE_INT]; + yield [Type::TYPE_FLOAT]; + yield [Type::TYPE_TRUE]; + yield [Type::TYPE_FALSE]; + yield [self::class]; + yield [new ReturnType(self::class)]; + yield [self::getReturnType(static fn(): string => '')]; + yield [self::getReturnType(static fn(): int => 0)]; + yield [self::getReturnType(static fn(): float => 0.0)]; + yield [self::getReturnType(static fn(): bool => false)]; + yield [self::getReturnType(static fn(): array => [])]; + yield [self::getReturnType(static fn(): object => new \stdClass())]; + yield 'union' => [[self::getReturnType(static fn(): int|string => 0)]]; + } + + public static function getNullableTypes(): iterable + { + yield [null]; + yield [Type::create(Type::TYPE_ANY)]; + yield [Type::create(Type::TYPE_VOID)]; + yield [Type::create(Type::TYPE_NULL)]; + yield [new Type(self::class, true)]; + yield [new ReturnType(self::class, true)]; + yield [Type::TYPE_ANY]; + yield [Type::TYPE_VOID]; + yield [Type::TYPE_NULL]; + yield 'nullable' => [self::getReturnType(static fn(): ?string => null)]; + yield 'mixed' => [self::getReturnType(static fn(): mixed => null)]; + yield 'void' => [self::getReturnType(static function (): void {})]; + yield 'union' => [self::getReturnType(static fn(): int|string|null => null)]; + } + + #[Test] + #[DataProvider('getNullableTypes')] + public function payloadWithoutValueDecoding(mixed $type): void + { + $encodedValues = EncodedValues::fromPayloadCollection(new \ArrayIterator([])); + + self::assertNull($encodedValues->getValue(0, $type)); + } + + #[Test] + #[DataProvider('getNotNullableTypes')] + public function payloadWithoutValueDecodingNotNullable(mixed $type): void + { + $encodedValues = EncodedValues::fromPayloadCollection(new \ArrayIterator([])); + + self::expectException(\LogicException::class); + self::expectExceptionMessage('DataConverter is not set'); + + $encodedValues->getValue(0, $type); + } + + private static function getReturnType(\Closure $closure): \ReflectionType + { + return (new \ReflectionFunction($closure))->getReturnType(); + } }