Skip to content

Commit cd3923a

Browse files
authored
Merge pull request #389 from durable-workflow/fix/replay-preserve-structured-failure
Preserve structured replay failures for standalone workers
2 parents f4cccb6 + a2f85fd commit cd3923a

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

src/V2/Support/FailureFactory.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,15 @@ public static function restoreForReplay(
249249
?int $fallbackCode = null,
250250
): Throwable {
251251
$normalized = self::normalizePayload($payload, $fallbackClass, $fallbackMessage, $fallbackCode);
252+
253+
if (
254+
array_key_exists('details', $normalized)
255+
|| is_string($normalized['details_payload_codec'] ?? null)
256+
|| array_key_exists('non_retryable', $normalized)
257+
) {
258+
return new RestoredWorkflowException($normalized);
259+
}
260+
252261
$class = $normalized['class'];
253262

254263
try {

tests/Unit/V2/FailureFactoryRestoreTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Exception;
1010
use RuntimeException;
1111
use Tests\TestCase;
12+
use Workflow\Serializers\Serializer;
13+
use Workflow\V2\Exceptions\RestoredWorkflowException;
1214
use TypeError;
1315
use Workflow\V2\Support\FailureFactory;
1416

@@ -68,4 +70,30 @@ public function testRestoresBaseException(): void
6870
$this->assertInstanceOf(Exception::class, $restored);
6971
$this->assertSame('base exception sanity check', $restored->getMessage());
7072
}
73+
74+
public function testReplayPreservesStructuredFailureMetadata(): void
75+
{
76+
$payload = [
77+
'class' => RuntimeException::class,
78+
'type' => 'planned.python.failure',
79+
'message' => 'planned python failure',
80+
'non_retryable' => true,
81+
'details_payload_codec' => 'avro',
82+
'details' => Serializer::serializeWithCodec('avro', ['label' => 'planned-python-failure']),
83+
];
84+
85+
$restored = FailureFactory::restoreForReplay($payload);
86+
87+
$this->assertInstanceOf(RestoredWorkflowException::class, $restored);
88+
$this->assertSame('planned python failure', $restored->getMessage());
89+
$failurePayload = $restored->failurePayload();
90+
91+
$this->assertSame(RuntimeException::class, $failurePayload['class'] ?? null);
92+
$this->assertSame('planned.python.failure', $failurePayload['type'] ?? null);
93+
$this->assertSame('planned python failure', $failurePayload['message'] ?? null);
94+
$this->assertSame(0, $failurePayload['code'] ?? null);
95+
$this->assertTrue($failurePayload['non_retryable'] ?? false);
96+
$this->assertSame('avro', $failurePayload['details_payload_codec'] ?? null);
97+
$this->assertSame($payload['details'], $failurePayload['details'] ?? null);
98+
}
7199
}

0 commit comments

Comments
 (0)