[AJDA-2706] Tolerate webhook recipients in Responses\Subscription#26
[AJDA-2706] Tolerate webhook recipients in Responses\Subscription#26ondrajodas wants to merge 5 commits intomainfrom
Conversation
|
There was a problem hiding this comment.
Pull request overview
This PR updates Responses\Subscription deserialization to support non-email recipients (webhooks) by introducing polymorphic recipient value objects, preventing failures when the API returns recipient.url instead of recipient.address.
Changes:
- Add
RecipientInterfaceplusEmailRecipientandWebhookRecipientresponse VOs. - Update
Responses\Subscriptionto instantiate the correct recipient type based onrecipient.channel, and addgetRecipient(): RecipientInterface(withgetRecipientAddress(): ?stringas BC behavior). - Extend unit test coverage and document the new API + BC note in the README.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Responses/Subscription.php | Branch recipient parsing by channel and expose a polymorphic getRecipient() API. |
| src/Responses/Recipient/RecipientInterface.php | Defines a common interface for response recipient VOs. |
| src/Responses/Recipient/EmailRecipient.php | Email recipient VO with getAddress() and CHANNEL constant. |
| src/Responses/Recipient/WebhookRecipient.php | Webhook recipient VO with getUrl() and CHANNEL constant. |
| tests/Responses/SubscriptionTest.php | Adds coverage for email/webhook recipients and unknown-channel behavior. |
| tests/Responses/Recipient/EmailRecipientTest.php | Unit test for EmailRecipient accessors and interface conformance. |
| tests/Responses/Recipient/WebhookRecipientTest.php | Unit test for WebhookRecipient accessors and interface conformance. |
| README.md | Documents the new polymorphic recipient API and the BC change for getRecipientAddress(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $channel = $data['channel']; | ||
| assert(is_string($channel)); | ||
| switch ($channel) { | ||
| case EmailRecipient::CHANNEL: | ||
| $address = $data['address']; | ||
| assert(is_string($address)); | ||
| return new EmailRecipient($address); | ||
| case WebhookRecipient::CHANNEL: | ||
| $url = $data['url']; | ||
| assert(is_string($url)); | ||
| return new WebhookRecipient($url); |
There was a problem hiding this comment.
createRecipient() relies on assert(is_string(...)) for runtime input validation. PHP assertions are often disabled in production, which can allow non-string values (or missing keys resulting in null) to flow into the switch/sprintf path and potentially emit warnings/deprecations that are not caught by the surrounding try/catch. Prefer explicit runtime checks (e.g. isset/array_key_exists + is_string) and throw a regular exception when the data is invalid, letting the existing constructor wrapper convert it to ClientException.
| $channel = $data['channel']; | |
| assert(is_string($channel)); | |
| switch ($channel) { | |
| case EmailRecipient::CHANNEL: | |
| $address = $data['address']; | |
| assert(is_string($address)); | |
| return new EmailRecipient($address); | |
| case WebhookRecipient::CHANNEL: | |
| $url = $data['url']; | |
| assert(is_string($url)); | |
| return new WebhookRecipient($url); | |
| if (!array_key_exists('channel', $data) || !is_string($data['channel'])) { | |
| throw new \InvalidArgumentException('Invalid recipient data: "channel" must be a string.'); | |
| } | |
| $channel = $data['channel']; | |
| switch ($channel) { | |
| case EmailRecipient::CHANNEL: | |
| if (!array_key_exists('address', $data) || !is_string($data['address'])) { | |
| throw new \InvalidArgumentException('Invalid recipient data: "address" must be a string.'); | |
| } | |
| return new EmailRecipient($data['address']); | |
| case WebhookRecipient::CHANNEL: | |
| if (!array_key_exists('url', $data) || !is_string($data['url'])) { | |
| throw new \InvalidArgumentException('Invalid recipient data: "url" must be a string.'); | |
| } | |
| return new WebhookRecipient($data['url']); |
There was a problem hiding this comment.
Fixed. Replaced the assert(is_string(...)) calls with an explicit extractString() helper that throws ClientException when a required field is missing or not a string. Behaves correctly even with assertions disabled in production.
Fixes deserialization in
Responses\Subscription, which previously hardcodedrecipient.addressand threwUnrecognized response: Undefined array key "address"when a project had any webhook subscription (recipient.urlinstead ofrecipient.address).Changes
Responses\Recipient\:RecipientInterfacewithgetChannel(): stringEmailRecipient(channelemail,getAddress(): string,EmailRecipient::CHANNELconstant)WebhookRecipient(channelwebhook,getUrl(): string,WebhookRecipient::CHANNELconstant)Subscriptionnow branches onrecipient.channeland instantiates the right concrete type. Unknown channels still throwClientExceptionvia the existingUnrecognized response: ...wrapper.Subscription::getRecipient(): RecipientInterfaceis the primary API.Subscription::getRecipientChannel(): stringunchanged (now a thin delegate).BC change
Subscription::getRecipientAddress()return type widens fromstringto?stringand returnsnullfor non-email channels (e.g. webhook). New code should prefergetRecipient()withinstanceof EmailRecipient/instanceof WebhookRecipient.Tests
Tests\Responses\SubscriptionTestcovers email recipient, webhook recipient, unknown-channel error, and the existing invalid-data path. Dedicated unit tests forEmailRecipientandWebhookRecipientadded.Follow-up
Unblocks AJDA-2596 (flow-migration-tool) — once released there, the loader can branch on
getRecipientChannel()and stop filtering webhooks out client-side.