diff --git a/README.md b/README.md index be47842..11ac5ac 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,29 @@ $subscriptionClient->deleteSubscription($created->getId()); value type matches whatever the API returns (for example `durationOvertimePercentage` returns `float`). +### Subscription response recipients + +`Responses\Subscription` exposes the recipient as a polymorphic Value Object via `getRecipient(): RecipientInterface`. Two concrete types live under `Keboola\NotificationClient\Responses\Recipient`: + +- `EmailRecipient` — `getChannel()` returns `'email'`, `getAddress(): string` +- `WebhookRecipient` — `getChannel()` returns `'webhook'`, `getUrl(): string` + +Use `instanceof` to narrow: + +```php +use Keboola\NotificationClient\Responses\Recipient\EmailRecipient; +use Keboola\NotificationClient\Responses\Recipient\WebhookRecipient; + +$recipient = $subscription->getRecipient(); +if ($recipient instanceof EmailRecipient) { + $email = $recipient->getAddress(); +} elseif ($recipient instanceof WebhookRecipient) { + $url = $recipient->getUrl(); +} +``` + +**BC change:** `Subscription::getRecipientAddress()` return type changed from `string` to `?string` and returns `null` for non-email channels. `getRecipientChannel(): string` is unchanged. + ## Development - Create an Azure service principal to download the required images and login: diff --git a/src/Responses/Recipient/EmailRecipient.php b/src/Responses/Recipient/EmailRecipient.php new file mode 100644 index 0000000..d388c12 --- /dev/null +++ b/src/Responses/Recipient/EmailRecipient.php @@ -0,0 +1,27 @@ +address = $address; + } + + public function getChannel(): string + { + return self::CHANNEL; + } + + public function getAddress(): string + { + return $this->address; + } +} diff --git a/src/Responses/Recipient/RecipientInterface.php b/src/Responses/Recipient/RecipientInterface.php new file mode 100644 index 0000000..c770dec --- /dev/null +++ b/src/Responses/Recipient/RecipientInterface.php @@ -0,0 +1,10 @@ +url = $url; + } + + public function getChannel(): string + { + return self::CHANNEL; + } + + public function getUrl(): string + { + return $this->url; + } +} diff --git a/src/Responses/Subscription.php b/src/Responses/Subscription.php index befa8a7..5a32912 100644 --- a/src/Responses/Subscription.php +++ b/src/Responses/Subscription.php @@ -5,6 +5,9 @@ namespace Keboola\NotificationClient\Responses; use Keboola\NotificationClient\Exception\ClientException; +use Keboola\NotificationClient\Responses\Recipient\EmailRecipient; +use Keboola\NotificationClient\Responses\Recipient\RecipientInterface; +use Keboola\NotificationClient\Responses\Recipient\WebhookRecipient; use Throwable; class Subscription @@ -13,10 +16,7 @@ class Subscription private string $event; /** @var array */ private array $filters; - /** @var string */ - private $recipientAddress; - /** @var string */ - private $recipientChannel; + private RecipientInterface $recipient; public function __construct(array $data) { @@ -27,13 +27,39 @@ public function __construct(array $data) fn(array $item) => new Filter($item), $data['filters'], )); - $this->recipientChannel = $data['recipient']['channel']; - $this->recipientAddress = $data['recipient']['address']; + $this->recipient = self::createRecipient($data['recipient']); } catch (Throwable $e) { throw new ClientException('Unrecognized response: ' . $e->getMessage(), 0, $e); } } + /** + * @param array $data + */ + private static function createRecipient(array $data): RecipientInterface + { + $channel = self::extractString($data, 'channel'); + switch ($channel) { + case EmailRecipient::CHANNEL: + return new EmailRecipient(self::extractString($data, 'address')); + case WebhookRecipient::CHANNEL: + return new WebhookRecipient(self::extractString($data, 'url')); + default: + throw new ClientException(sprintf('Unknown recipient channel "%s"', $channel)); + } + } + + /** + * @param array $data + */ + private static function extractString(array $data, string $key): string + { + if (!array_key_exists($key, $data) || !is_string($data[$key])) { + throw new ClientException(sprintf('Recipient field "%s" must be a string', $key)); + } + return $data[$key]; + } + public function getId(): string { return $this->id; @@ -52,13 +78,24 @@ public function getFilters(): array return $this->filters; } - public function getRecipientAddress(): string + public function getRecipient(): RecipientInterface { - return $this->recipientAddress; + return $this->recipient; } public function getRecipientChannel(): string { - return $this->recipientChannel; + return $this->recipient->getChannel(); + } + + /** + * BC: previously `: string`. Now `: ?string`. Returns null for non-email channels (e.g. webhook). + */ + public function getRecipientAddress(): ?string + { + if ($this->recipient instanceof EmailRecipient) { + return $this->recipient->getAddress(); + } + return null; } } diff --git a/tests/Responses/Recipient/EmailRecipientTest.php b/tests/Responses/Recipient/EmailRecipientTest.php new file mode 100644 index 0000000..e7d7113 --- /dev/null +++ b/tests/Responses/Recipient/EmailRecipientTest.php @@ -0,0 +1,20 @@ +getChannel()); + self::assertSame('john.doe@example.com', $recipient->getAddress()); + } +} diff --git a/tests/Responses/Recipient/WebhookRecipientTest.php b/tests/Responses/Recipient/WebhookRecipientTest.php new file mode 100644 index 0000000..0e942fd --- /dev/null +++ b/tests/Responses/Recipient/WebhookRecipientTest.php @@ -0,0 +1,20 @@ +getChannel()); + self::assertSame('https://example.com/webhook', $recipient->getUrl()); + } +} diff --git a/tests/Responses/SubscriptionTest.php b/tests/Responses/SubscriptionTest.php index 0325ad4..7791f52 100644 --- a/tests/Responses/SubscriptionTest.php +++ b/tests/Responses/SubscriptionTest.php @@ -5,19 +5,21 @@ namespace Keboola\NotificationClient\Tests\Responses; use Keboola\NotificationClient\Exception\ClientException; +use Keboola\NotificationClient\Responses\Recipient\EmailRecipient; +use Keboola\NotificationClient\Responses\Recipient\WebhookRecipient; use Keboola\NotificationClient\Responses\Subscription; use PHPUnit\Framework\TestCase; class SubscriptionTest extends TestCase { - public function testAccessors(): void + public function testEmailRecipientAccessors(): void { $data = [ 'id' => '123', 'event' => 'some_event', 'recipient' => [ - 'channel' => 'boo', - 'address' => 'foo', + 'channel' => 'email', + 'address' => 'john.doe@example.com', ], 'filters' => [ 2 => [ @@ -28,12 +30,58 @@ public function testAccessors(): void ]; $subscription = new Subscription($data); self::assertSame('123', $subscription->getId()); - self::assertSame('boo', $subscription->getRecipientChannel()); - self::assertSame('foo', $subscription->getRecipientAddress()); self::assertSame('some_event', $subscription->getEvent()); + self::assertSame('email', $subscription->getRecipientChannel()); + self::assertSame('john.doe@example.com', $subscription->getRecipientAddress()); self::assertCount(1, $subscription->getFilters()); self::assertSame('bar', $subscription->getFilters()[0]->getField()); self::assertSame('Kochba', $subscription->getFilters()[0]->getValue()); + + $recipient = $subscription->getRecipient(); + self::assertInstanceOf(EmailRecipient::class, $recipient); + self::assertSame('email', $recipient->getChannel()); + self::assertSame('john.doe@example.com', $recipient->getAddress()); + } + + public function testWebhookRecipientAccessors(): void + { + $data = [ + 'id' => '29746', + 'event' => 'job-succeeded', + 'recipient' => [ + 'channel' => 'webhook', + 'url' => 'https://asd.as', + ], + 'filters' => [], + ]; + $subscription = new Subscription($data); + self::assertSame('29746', $subscription->getId()); + self::assertSame('job-succeeded', $subscription->getEvent()); + self::assertSame('webhook', $subscription->getRecipientChannel()); + self::assertNull($subscription->getRecipientAddress()); + self::assertSame([], $subscription->getFilters()); + + $recipient = $subscription->getRecipient(); + self::assertInstanceOf(WebhookRecipient::class, $recipient); + self::assertSame('webhook', $recipient->getChannel()); + self::assertSame('https://asd.as', $recipient->getUrl()); + } + + public function testUnknownChannelThrows(): void + { + $data = [ + 'id' => '1', + 'event' => 'some_event', + 'recipient' => [ + 'channel' => 'boo', + 'address' => 'foo', + ], + 'filters' => [], + ]; + $this->expectException(ClientException::class); + $this->expectExceptionMessageMatches('#Unrecognized response:.*?boo#'); + $this->expectExceptionCode(0); + new Subscription($data); } public function testInvalidData(): void diff --git a/tests/SubscriptionClientFunctionalTest.php b/tests/SubscriptionClientFunctionalTest.php index 0b044d5..728b590 100644 --- a/tests/SubscriptionClientFunctionalTest.php +++ b/tests/SubscriptionClientFunctionalTest.php @@ -88,7 +88,7 @@ public function testCreateSubscriptionHeaders(): void new Response( 200, ['Content-Type' => 'application/json'], - '{"id": "1", "event": "2", "filters": [], "recipient": {"channel": "foo", "address": "bar"}}', + '{"id": "1", "event": "2", "filters": [], "recipient": {"channel": "email", "address": "bar"}}', ), ]); // Add the history middleware to the handler stack.