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
HistoryReader 100.00% 18 / 18 100.00% 3 / 3 8 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 fetchHistory 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\Entity\EntityId;
9use Stewart\Contracts\Exception\HistoryException;
10use Stewart\Contracts\History\Collection\HistoricalStateCollection;
11use Stewart\Contracts\History\EntityStateHistory;
12use Stewart\Contracts\History\HistoryQuery;
13use Stewart\Contracts\Time\Clock;
14use Stewart\Runtime\Exception\TransportError;
15use Stewart\Runtime\Exception\TransportException;
16use Stewart\Runtime\Ipc\Message\HistoryRequest;
17use Stewart\Runtime\Ipc\Transport;
18use Stewart\Runtime\Ipc\WorkerSettings;
19use Stewart\Runtime\Model\ResourceScope;
20use Stewart\Runtime\Worker\ConnectionStatus;
21use Stewart\Runtime\Worker\PendingRequests;
22use Stewart\Support\Time\Deadlines;
23use Throwable;
24
25final readonly class HistoryReader
26{
27    public function __construct(
28        private Transport $transport,
29        private PendingRequests $pending,
30        private ConnectionStatus $connection,
31        private Deadlines $deadlines,
32        private Clock $clock,
33        private WorkerSettings $workerSettings,
34    ) {}
35
36    /** @throws HistoryException */
37    public function fetchHistory(ResourceScope $scope, EntityId $entityId, HistoryQuery $query): EntityStateHistory
38    {
39        $window = $query->resolveWindowAt($this->clock->getNow());
40
41        if (!$this->connection->connected) {
42            throw HistoryException::unreachable($entityId, 'Home Assistant is disconnected');
43        }
44
45        $pending = $this->pending->open(
46            HistoricalStateCollection::class,
47            static fn(string $detail): HistoryException => HistoryException::unreachable($entityId, $detail),
48        );
49
50        try {
51            $this->sendRequest(new HistoryRequest($pending->correlationId, $scope, $entityId, $window, $query->detail));
52
53            return new EntityStateHistory($entityId, $window, $pending->getFuture()->await($this->deadlines->timeout($this->workerSettings->callTimeout)));
54        } catch (CancelledException $e) {
55            throw HistoryException::timedOut($entityId, \sprintf('no answer from the broker within %s', $this->workerSettings->callTimeout), $e);
56        } finally {
57            $this->pending->forget($pending->correlationId);
58        }
59    }
60
61    /** @throws HistoryException */
62    private function sendRequest(HistoryRequest $request): void
63    {
64        try {
65            $this->transport->send($request);
66        } catch (Throwable $e) {
67            throw $e instanceof TransportException && $e->reason === TransportError::Unencodable
68                ? HistoryException::rejected($request->entityId, $e->getMessage(), null, $e)
69                : HistoryException::unreachable($request->entityId, 'the broker is gone: ' . $e->getMessage(), $e);
70        }
71    }
72}