-
Notifications
You must be signed in to change notification settings - Fork 52
Open
Labels
BugSomething isn't workingSomething isn't working
Description
What are you really trying to do?
I am starting the workflow
Describe the bug
If an exception is thrown from PayloadConverter, it cannot be processed and is not visible in the logs or in ui temporal itself. I would like to be able to send this exception to sentry/logs
Minimal Reproduction
ChildWorkflow Case
<?php
final class BRequest
{
public function __construct(
public string $id,
) {
}
}
#[WorkflowInterface]
#[AssignWorker('mdm.client_verification.workflow')]
final class BWorkflow
{
#[WorkflowMethod('BWorkflow')]
public function start(BRequest $request): Generator
{
yield Workflow::timer(CarbonInterval::minutes(1));
}
}
#[WorkflowInterface]
#[AssignWorker('mdm.client_verification.workflow')]
final class AWorkflow
{
#[WorkflowMethod('AWorkflow')]
public function start(): Generator
{
$workflow = Workflow::newChildWorkflowStub(
BWorkflow::class,
Workflow\ChildWorkflowOptions::new()
->withTaskQueue('mdm.client_verification.workflow')
->withNamespace(Workflow::getInfo()->namespace)
);
yield $workflow->start(new BRequest('123'));
}
}DataConverter
<?php
/**
* Temporal Bundle
*
* @author Vlad Shashkov <[email protected]>
* @copyright Copyright (c) 2023, The Vanta
*/
declare(strict_types=1);
namespace Vanta\Integration\Symfony\Temporal\DataConverter;
use App\ClientVerification\Workflow\Join\BRequest;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer as ObjectNormalizer;
use Symfony\Component\Serializer\SerializerInterface as Serializer;
use Temporal\Api\Common\V1\Payload;
use Temporal\DataConverter\EncodingKeys;
use Temporal\DataConverter\JsonConverter;
use Temporal\DataConverter\PayloadConverterInterface as PayloadConverter;
use Temporal\DataConverter\Type;
use Temporal\Exception\DataConverterException;
use Throwable;
final readonly class SymfonySerializerDataConverter implements PayloadConverter
{
private const INPUT_TYPE = 'symfony.serializer.type';
public function __construct(
private Serializer $serializer,
private PayloadConverter $payloadConverter = new JsonConverter(),
) {
}
public function getEncodingType(): string
{
return EncodingKeys::METADATA_ENCODING_JSON;
}
public function toPayload($value): Payload
{
$metadata = [
EncodingKeys::METADATA_ENCODING_KEY => $this->getEncodingType(),
];
$context = [ObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true];
if (is_object($value)) {
$metadata[self::INPUT_TYPE] = $value::class;
}
try {
$data = $this->serializer->serialize($value, 'json', $context);
} catch (Throwable $e) {
throw new DataConverterException($e->getMessage(), $e->getCode(), $e);
}
$payload = new Payload();
$payload->setMetadata($metadata);
$payload->setData($data);
return $payload;
}
public function fromPayload(Payload $payload, Type $type): mixed
{
if ("null" == $payload->getData() && $type->allowsNull()) {
return null;
}
/** @var string|null $inputType */
$inputType = $payload->getMetadata()[self::INPUT_TYPE] ?? null;
if ($inputType == BRequest::class) {
// dump
throw new DataConverterException('BAM', 3, null);
}
if (!$type->isClass() && $inputType == null) {
return $this->payloadConverter->fromPayload($payload, $type);
}
try {
return $this->serializer->deserialize($payload->getData(), $inputType ?? $type->getName(), 'json');
} catch (Throwable $e) {
throw new DataConverterException($e->getMessage(), $e->getCode(), $e);
}
}
}Event history
ContinueAsNew Case
<?php
#[WorkflowInterface]
#[AssignWorker('mdm.client_verification.workflow')]
final class AWorkflow
{
#[WorkflowMethod('AWorkflow')]
public function start(?stdClass $foo = null): Generator
{
yield Workflow::timer(CarbonInterval::second(30));
$workflow = Workflow::newContinueAsNewStub(
self::class,
ContinueAsNewOptions::new()
->withTaskQueue('mdm.client_verification.workflow')
);
yield $workflow->start(new stdClass());
}
}DataConverter
<?php
/**
* Temporal Bundle
*
* @author Vlad Shashkov <[email protected]>
* @copyright Copyright (c) 2023, The Vanta
*/
declare(strict_types=1);
namespace Vanta\Integration\Symfony\Temporal\DataConverter;
use App\ClientVerification\Workflow\Join\BRequest;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer as ObjectNormalizer;
use Symfony\Component\Serializer\SerializerInterface as Serializer;
use Temporal\Api\Common\V1\Payload;
use Temporal\DataConverter\EncodingKeys;
use Temporal\DataConverter\JsonConverter;
use Temporal\DataConverter\PayloadConverterInterface as PayloadConverter;
use Temporal\DataConverter\Type;
use Temporal\Exception\DataConverterException;
use Throwable;
final readonly class SymfonySerializerDataConverter implements PayloadConverter
{
private const INPUT_TYPE = 'symfony.serializer.type';
public function __construct(
private Serializer $serializer,
private PayloadConverter $payloadConverter = new JsonConverter(),
) {
}
public function getEncodingType(): string
{
return EncodingKeys::METADATA_ENCODING_JSON;
}
public function toPayload($value): Payload
{
$metadata = [
EncodingKeys::METADATA_ENCODING_KEY => $this->getEncodingType(),
];
$context = [ObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true];
if (is_object($value)) {
$metadata[self::INPUT_TYPE] = $value::class;
}
try {
$data = $this->serializer->serialize($value, 'json', $context);
} catch (Throwable $e) {
throw new DataConverterException($e->getMessage(), $e->getCode(), $e);
}
$payload = new Payload();
$payload->setMetadata($metadata);
$payload->setData($data);
return $payload;
}
public function fromPayload(Payload $payload, Type $type): mixed
{
if ("null" == $payload->getData() && $type->allowsNull()) {
return null;
}
/** @var string|null $inputType */
$inputType = $payload->getMetadata()[self::INPUT_TYPE] ?? null;
if ($inputType == \stdClass::class) {
// workflow broken 💥
throw new DataConverterException('BAM', 3, null);
}
if (!$type->isClass() && $inputType == null) {
return $this->payloadConverter->fromPayload($payload, $type);
}
try {
return $this->serializer->deserialize($payload->getData(), $inputType ?? $type->getName(), 'json');
} catch (Throwable $e) {
throw new DataConverterException($e->getMessage(), $e->getCode(), $e);
}
}
}Environment/Versions
- Temporal sdk 2.15.1
- RoadRunner 2025.1.2
- Temporal server 1.24.2
Metadata
Metadata
Assignees
Labels
BugSomething isn't workingSomething isn't working


