Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
27 changes: 27 additions & 0 deletions src/Responses/Recipient/EmailRecipient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Keboola\NotificationClient\Responses\Recipient;

class EmailRecipient implements RecipientInterface
{
public const CHANNEL = 'email';

private string $address;

public function __construct(string $address)
{
$this->address = $address;
}

public function getChannel(): string
{
return self::CHANNEL;
}

public function getAddress(): string
{
return $this->address;
}
}
10 changes: 10 additions & 0 deletions src/Responses/Recipient/RecipientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Keboola\NotificationClient\Responses\Recipient;

interface RecipientInterface
{
public function getChannel(): string;
}
27 changes: 27 additions & 0 deletions src/Responses/Recipient/WebhookRecipient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Keboola\NotificationClient\Responses\Recipient;

class WebhookRecipient implements RecipientInterface
{
public const CHANNEL = 'webhook';

private string $url;

public function __construct(string $url)
{
$this->url = $url;
}

public function getChannel(): string
{
return self::CHANNEL;
}

public function getUrl(): string
{
return $this->url;
}
}
55 changes: 46 additions & 9 deletions src/Responses/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,10 +16,7 @@ class Subscription
private string $event;
/** @var array<Filter> */
private array $filters;
/** @var string */
private $recipientAddress;
/** @var string */
private $recipientChannel;
private RecipientInterface $recipient;

public function __construct(array $data)
{
Expand All @@ -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<string, mixed> $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<string, mixed> $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;
Expand All @@ -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;
}
}
20 changes: 20 additions & 0 deletions tests/Responses/Recipient/EmailRecipientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Keboola\NotificationClient\Tests\Responses\Recipient;

use Keboola\NotificationClient\Responses\Recipient\EmailRecipient;
use Keboola\NotificationClient\Responses\Recipient\RecipientInterface;
use PHPUnit\Framework\TestCase;

class EmailRecipientTest extends TestCase
{
public function testAccessors(): void
{
$recipient = new EmailRecipient('john.doe@example.com');
self::assertInstanceOf(RecipientInterface::class, $recipient);
self::assertSame('email', $recipient->getChannel());
self::assertSame('john.doe@example.com', $recipient->getAddress());
}
}
20 changes: 20 additions & 0 deletions tests/Responses/Recipient/WebhookRecipientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Keboola\NotificationClient\Tests\Responses\Recipient;

use Keboola\NotificationClient\Responses\Recipient\RecipientInterface;
use Keboola\NotificationClient\Responses\Recipient\WebhookRecipient;
use PHPUnit\Framework\TestCase;

class WebhookRecipientTest extends TestCase
{
public function testAccessors(): void
{
$recipient = new WebhookRecipient('https://example.com/webhook');
self::assertInstanceOf(RecipientInterface::class, $recipient);
self::assertSame('webhook', $recipient->getChannel());
self::assertSame('https://example.com/webhook', $recipient->getUrl());
}
}
58 changes: 53 additions & 5 deletions tests/Responses/SubscriptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 => [
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/SubscriptionClientFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down