Lines 89.47% 17 / 19
Methods 85.71% 6 / 7
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 requestExposure 100.00% 2 / 2 100.00% 1 / 1 1
 requestReconfiguration 0.00% 0 / 2 0.00% 0 / 1 2
 requestUpdate 100.00% 2 / 2 100.00% 1 / 1 1
 requestRemoval 100.00% 2 / 2 100.00% 1 / 1 1
 describeUnreachable 100.00% 1 / 1 100.00% 1 / 1 1
 awaitAnswer 100.00% 9 / 9 100.00% 1 / 1 4
32final readonly class ExposureRequester
33{
34    public function __construct(
35        private Transport $transport,
36        private PendingRequests $pending,
37        private Deadlines $deadlines,
38        private WorkerSettings $workerSettings,
39    ) {}
40
41    /** @throws ExposureException */
42    public function requestExposure(
43        ResourceScope $scope,
44        ExposedEntityKey $key,
45        ExposureConfig $config,
46        ?DeviceInfo $device,
47        ExposedStateChange $change,
48    ): ?ExposedEntitySnapshot {
49        $pending = $this->pending->open(ExposeEntityResult::class, self::describeUnreachable($key));
50
51        return $this->awaitAnswer($pending, $key, new ExposeEntityRequest($pending->correlationId, $scope, $key, $config, $device, $change))->snapshot;
52    }
53
54    /** @throws ExposureException */
55    public function requestReconfiguration(ResourceScope $scope, ExposedEntityKey $key, ExposureConfig $config): ?ExposedEntitySnapshot
56    {
57        $pending = $this->pending->open(ExposeEntityResult::class, self::describeUnreachable($key));
58
59        return $this->awaitAnswer($pending, $key, new ReconfigureExposedEntityRequest($pending->correlationId, $scope, $key, $config))->snapshot;
60    }
61
62    /** @throws ExposureException */
63    public function requestUpdate(ResourceScope $scope, ExposedEntityKey $key, ExposedStateChange $change): void
64    {
65        $pending = $this->pending->open(ExposureAcknowledged::class, self::describeUnreachable($key));
66        $this->awaitAnswer($pending, $key, new UpdateExposedEntityRequest($pending->correlationId, $scope, $key, $change));
67    }
68
69    /** @throws ExposureException */
70    public function requestRemoval(ResourceScope $scope, ExposedEntityKey $key): void
71    {
72        $pending = $this->pending->open(ExposureAcknowledged::class, self::describeUnreachable($key));
73        $this->awaitAnswer($pending, $key, new RemoveExposedEntityRequest($pending->correlationId, $scope, $key));
74    }
75
76    /** @return Closure(string): ExposureException */
77    private static function describeUnreachable(ExposedEntityKey $key): Closure
78    {
79        return static fn(string $detail): ExposureException => ExposureException::unreachable($key, $detail);
80    }
81
82    /**
83     * @template TResult of object
84     *
85     * @param PendingRequest<TResult> $pending
86     * @return TResult
87     * @throws ExposureException
88     */
89    private function awaitAnswer(PendingRequest $pending, ExposedEntityKey $key, WorkerMessage $request): object
90    {
91        try {
92            $this->transport->send($request);
93
94            return $pending->getFuture()->await($this->deadlines->timeout($this->workerSettings->callTimeout));
95        } catch (TransportException $e) {
96            throw $e->reason === TransportError::Unencodable
97                ? ExposureException::stateInvalid($e->getMessage())
98                : ExposureException::unreachable($key, 'the broker is gone: ' . $e->getMessage(), $e);
99        } catch (CancelledException) {
100            throw ExposureException::createForReason(ExposureError::TimedOut, key: $key->value, timeout: (string) $this->workerSettings->callTimeout);
101        } finally {
102            $this->pending->forget($pending->correlationId);
103        }
104    }
105}