Skip to content

Commit bafad2b

Browse files
v2: relax control-plane payload envelope to accept any registered codec
`PayloadEnvelopeResolver::resolveToArray()` is the shared input decoder for signal, query, and update surfaces. It used to hard-reject anything other than the "json" codec, which blocks the Avro (#330) default codec from reaching those surfaces and made the error message surprise clients that already carry Avro/PHP-closure payloads everywhere else. Change it to decode with `Serializer::unserializeWithCodec($codec, $blob)` and validate that the result is an array. That accepts json, avro, and the two legacy PHP closure codecs, and surfaces codec-specific decode errors as a friendly validation message instead of a bare exception. Add a PayloadEnvelopeResolverTest covering: - null / empty short-circuit - positional-array passthrough - json + legacy-Y round trips - avro round trip (skipped when apache/avro is unavailable) - unknown codec rejection - non-array and malformed blob rejection Partial progress on #331 (signal / query / update — items 1–3 of the surface list). The remaining items (child workflow propagation, continue-as-new, schedule input, history event payloads, Waterline and CLI rendering, and history export) are still open.
1 parent 2a2fc73 commit bafad2b

2 files changed

Lines changed: 117 additions & 8 deletions

File tree

src/V2/Support/PayloadEnvelopeResolver.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ final class PayloadEnvelopeResolver
3535
* Used for control-plane surfaces (signal, query, update) where the
3636
* package API expects a PHP array, not a codec-tagged blob. Accepts
3737
* either a plain array of positional arguments or a {codec, blob}
38-
* envelope — when an envelope is received, the blob is decoded using
39-
* the declared codec. Only the "json" codec is supported for array
40-
* surfaces; other codecs cannot be losslessly represented as PHP arrays.
38+
* envelope. When an envelope is received, the blob is decoded using
39+
* the declared codec — any codec in the {@see CodecRegistry} that can
40+
* round-trip an array is accepted (json, avro, and the legacy PHP
41+
* closure codecs). The decoded value must be an array.
4142
*
4243
* @param mixed $input the `input` field from a validated request
4344
* @return array<int|string, mixed> arguments (positional or named)
@@ -60,18 +61,19 @@ public static function resolveToArray($input, string $field = 'input'): array
6061

6162
$envelope = self::resolveExplicitEnvelope($input, $field);
6263

63-
if ($envelope['codec'] !== 'json') {
64+
try {
65+
$decoded = Serializer::unserializeWithCodec($envelope['codec'], $envelope['blob']);
66+
} catch (\Throwable $e) {
6467
throw ValidationException::withMessages([
65-
$field . '.codec' => [sprintf(
66-
'Only the "json" codec is supported for %s on this surface. Got "%s".',
68+
$field . '.blob' => [sprintf(
69+
'The %s envelope blob could not be decoded with codec "%s": %s',
6770
$field,
6871
$envelope['codec'],
72+
$e->getMessage(),
6973
)],
7074
]);
7175
}
7276

73-
$decoded = json_decode($envelope['blob'], true);
74-
7577
if (! is_array($decoded)) {
7678
throw ValidationException::withMessages([
7779
$field . '.blob' => [sprintf('The %s envelope blob must decode to an array.', $field)],
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\V2;
6+
7+
use Illuminate\Validation\ValidationException;
8+
use Tests\TestCase;
9+
use Workflow\Serializers\Serializer;
10+
use Workflow\V2\Support\PayloadEnvelopeResolver;
11+
12+
final class PayloadEnvelopeResolverTest extends TestCase
13+
{
14+
public function testResolveToArrayReturnsEmptyForNullOrEmpty(): void
15+
{
16+
$this->assertSame([], PayloadEnvelopeResolver::resolveToArray(null));
17+
$this->assertSame([], PayloadEnvelopeResolver::resolveToArray([]));
18+
}
19+
20+
public function testResolveToArrayReturnsPositionalArrayUnchanged(): void
21+
{
22+
$this->assertSame(
23+
['alpha', 'beta'],
24+
PayloadEnvelopeResolver::resolveToArray(['alpha', 'beta']),
25+
);
26+
}
27+
28+
public function testResolveToArrayDecodesJsonEnvelope(): void
29+
{
30+
$envelope = [
31+
'codec' => 'json',
32+
'blob' => Serializer::serializeWithCodec('json', ['a', 'b', 42]),
33+
];
34+
35+
$this->assertSame(
36+
['a', 'b', 42],
37+
PayloadEnvelopeResolver::resolveToArray($envelope),
38+
);
39+
}
40+
41+
public function testResolveToArrayDecodesLegacyYEnvelope(): void
42+
{
43+
$envelope = [
44+
'codec' => 'workflow-serializer-y',
45+
'blob' => Serializer::serializeWithCodec('workflow-serializer-y', ['a', 'b']),
46+
];
47+
48+
$this->assertSame(
49+
['a', 'b'],
50+
PayloadEnvelopeResolver::resolveToArray($envelope),
51+
);
52+
}
53+
54+
public function testResolveToArrayDecodesAvroEnvelopeWhenInstalled(): void
55+
{
56+
if (! class_exists(\Apache\Avro\Schema\AvroSchema::class)) {
57+
$this->markTestSkipped('apache/avro package is not installed in this environment.');
58+
}
59+
60+
$envelope = [
61+
'codec' => 'avro',
62+
'blob' => Serializer::serializeWithCodec('avro', ['hello', 123]),
63+
];
64+
65+
$this->assertSame(
66+
['hello', 123],
67+
PayloadEnvelopeResolver::resolveToArray($envelope),
68+
);
69+
}
70+
71+
public function testResolveToArrayRejectsUnknownCodec(): void
72+
{
73+
$this->expectException(ValidationException::class);
74+
$this->expectExceptionMessage('Unknown payload codec');
75+
76+
PayloadEnvelopeResolver::resolveToArray([
77+
'codec' => 'does-not-exist',
78+
'blob' => 'xxx',
79+
]);
80+
}
81+
82+
public function testResolveToArrayRejectsNonArrayBlobPayload(): void
83+
{
84+
$envelope = [
85+
'codec' => 'json',
86+
'blob' => '"just a string"',
87+
];
88+
89+
$this->expectException(ValidationException::class);
90+
$this->expectExceptionMessage('must decode to an array');
91+
92+
PayloadEnvelopeResolver::resolveToArray($envelope);
93+
}
94+
95+
public function testResolveToArrayRejectsCorruptBlob(): void
96+
{
97+
$envelope = [
98+
'codec' => 'json',
99+
'blob' => '{not-valid-json',
100+
];
101+
102+
$this->expectException(ValidationException::class);
103+
$this->expectExceptionMessage('could not be decoded with codec "json"');
104+
105+
PayloadEnvelopeResolver::resolveToArray($envelope);
106+
}
107+
}

0 commit comments

Comments
 (0)