Lines 100.00% 18 / 18
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
EventFirer 100.00% 18 / 18 100.00% 3 / 3 8 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 fireEvent 100.00% 12 / 12 100.00% 1 / 1 3
 sendRequest 100.00% 5 / 5 100.00% 1 / 1 4
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Worker\Context;
6
7use Amp\CancelledException;
8use Stewart\Contracts\Exception\EventFireException;
9use Stewart\Contracts\State\EventContext;
10use Stewart\Runtime\Exception\TransportError;
11use Stewart\Runtime\Exception\TransportException;
12use Stewart\Runtime\Ipc\Message\EventFireRequest;
13use Stewart\Runtime\Ipc\Transport;
14use Stewart\Runtime\Ipc\WorkerSettings;
15use Stewart\Runtime\Model\ResourceScope;
16use Stewart\Runtime\Worker\ConnectionStatus;
17use Stewart\Runtime\Worker\PendingRequests;
18use Stewart\Support\Time\Deadlines;
19use Stewart\Support\Wire\EventPayload;
20use Throwable;
21
22final readonly class EventFirer
23{
24    public function __construct(
25        private Transport $transport,
26        private PendingRequests $pending,
27        private ConnectionStatus $connection,
28        private Deadlines $deadlines,
29        private WorkerSettings $workerSettings,
30    ) {}
31
32    /** @throws EventFireException */
33    public function fireEvent(ResourceScope $scope, EventPayload $payload): EventContext
34    {
35        if (!$this->connection->connected) {
36            throw EventFireException::unreachable($payload->eventType, 'Home Assistant is disconnected');
37        }
38
39        $eventType = $payload->eventType;
40        $fire = $this->pending->open(
41            EventContext::class,
42            static fn(string $detail): EventFireException => EventFireException::unreachable($eventType, $detail),
43        );
44
45        try {
46            $this->sendRequest(new EventFireRequest($fire->correlationId, $scope, $payload->eventType, $payload->data));
47
48            return $fire->getFuture()->await($this->deadlines->timeout($this->workerSettings->callTimeout));
49        } catch (CancelledException $e) {
50            throw EventFireException::timedOut($payload->eventType, \sprintf('no answer from the broker within %s', $this->workerSettings->callTimeout), $e);
51        } finally {
52            $this->pending->forget($fire->correlationId);
53        }
54    }
55
56    /** @throws EventFireException */
57    private function sendRequest(EventFireRequest $request): void
58    {
59        try {
60            $this->transport->send($request);
61        } catch (Throwable $e) {
62            throw $e instanceof TransportException && $e->reason === TransportError::Unencodable
63                ? EventFireException::rejected($request->eventType, $e->getMessage(), null, $e)
64                : EventFireException::unreachable($request->eventType, 'the broker is gone: ' . $e->getMessage(), $e);
65        }
66    }
67}