Lines 0.00% 0 / 32
Methods 0.00% 0 / 5
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 0.00% 0 / 1 0.00% 0 / 1 2
 callService 0.00% 0 / 1 0.00% 0 / 1 2
 callServiceForResponse 0.00% 0 / 1 0.00% 0 / 1 2
 sendRequestAndAwait 0.00% 0 / 24 0.00% 0 / 1 12
 sendRequest 0.00% 0 / 5 0.00% 0 / 1 20
24final readonly class ServiceCaller
25{
26    public function __construct(
27        private Transport $transport,
28        private PendingRequests $pending,
29        private ConnectionStatus $connection,
30        private Deadlines $deadlines,
31        private WorkerSettings $workerSettings,
32    ) {}
33
34    /** @throws ServiceCallException */
35    public function callService(ResourceScope $scope, string $domain, string $service, ServiceFields $data, ?ServiceTarget $target): EventContext
36    {
37        return $this->sendRequestAndAwait($scope, $domain, $service, $data, $target, returnResponse: false)->context ?? EventContext::unknown();
38    }
39
40    /** @throws ServiceCallException */
41    public function callServiceForResponse(ResourceScope $scope, string $domain, string $service, ServiceFields $data, ?ServiceTarget $target): ServiceResponse
42    {
43        return $this->sendRequestAndAwait($scope, $domain, $service, $data, $target, returnResponse: true);
44    }
45
46    /** @throws ServiceCallException */
47    private function sendRequestAndAwait(
48        ResourceScope $scope,
49        string $domain,
50        string $service,
51        ServiceFields $data,
52        ?ServiceTarget $target,
53        bool $returnResponse,
54    ): ServiceResponse {
55        if (!$this->connection->connected) {
56            throw ServiceCallException::unreachable($domain, $service, 'Home Assistant is disconnected');
57        }
58
59        $call = $this->pending->open(
60            ServiceResponse::class,
61            static fn(string $detail): ServiceCallException => ServiceCallException::unreachable($domain, $service, $detail),
62        );
63
64        try {
65            $this->sendRequest(new ServiceCallRequest(
66                correlationId: $call->correlationId,
67                scope: $scope,
68                domain: $domain,
69                service: $service,
70                data: $data->fields,
71                target: $target,
72                returnResponse: $returnResponse,
73            ));
74
75            return $call->getFuture()->await($this->deadlines->timeout($this->workerSettings->callTimeout));
76        } catch (CancelledException $e) {
77            throw ServiceCallException::timedOut(
78                $domain,
79                $service,
80                \sprintf('no answer from the broker within %s', $this->workerSettings->callTimeout),
81                $e,
82            );
83        } finally {
84            $this->pending->forget($call->correlationId);
85        }
86    }
87
88    /** @throws ServiceCallException */
89    private function sendRequest(ServiceCallRequest $request): void
90    {
91        try {
92            $this->transport->send($request);
93        } catch (Throwable $e) {
94            throw $e instanceof TransportException && $e->reason === TransportError::Unencodable
95                ? ServiceCallException::rejected($request->domain, $request->service, $e->getMessage(), null, $e)
96                : ServiceCallException::unreachable($request->domain, $request->service, 'the broker is gone: ' . $e->getMessage(), $e);
97        }
98    }
99}