Lines 100.00% 26 / 26
Functions and Methods 100.00% 15 / 15
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
WorkerEntityExposure 100.00% 26 / 26 100.00% 15 / 15 18 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 forApp 100.00% 1 / 1 100.00% 1 / 1 1
 exposeSensor 100.00% 1 / 1 100.00% 1 / 1 1
 exposeBinarySensor 100.00% 1 / 1 100.00% 1 / 1 1
 exposeSwitch 100.00% 1 / 1 100.00% 1 / 1 1
 exposeButton 100.00% 1 / 1 100.00% 1 / 1 1
 exposeNumber 100.00% 1 / 1 100.00% 1 / 1 1
 exposeSelect 100.00% 1 / 1 100.00% 1 / 1 1
 exposeText 100.00% 1 / 1 100.00% 1 / 1 1
 exposeTime 100.00% 1 / 1 100.00% 1 / 1 1
 exposeDate 100.00% 1 / 1 100.00% 1 / 1 1
 exposeDateTime 100.00% 1 / 1 100.00% 1 / 1 1
 exposeAction 100.00% 1 / 1 100.00% 1 / 1 1
 claimKey 100.00% 5 / 5 100.00% 1 / 1 2
 exposeHandle 100.00% 8 / 8 100.00% 1 / 1 3
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Worker;
6
7use Stewart\Contracts\App\AppId;
8use Stewart\Contracts\Exception\ExposureException;
9use Stewart\Contracts\Exposure\Action\ActionConfig;
10use Stewart\Contracts\Exposure\Action\ExposedAction;
11use Stewart\Contracts\Exposure\BinarySensorConfig;
12use Stewart\Contracts\Exposure\ButtonConfig;
13use Stewart\Contracts\Exposure\DateConfig;
14use Stewart\Contracts\Exposure\DateTimeConfig;
15use Stewart\Contracts\Exposure\DeviceInfo;
16use Stewart\Contracts\Exposure\EntityExposure;
17use Stewart\Contracts\Exposure\ExposedBinarySensor;
18use Stewart\Contracts\Exposure\ExposedButton;
19use Stewart\Contracts\Exposure\ExposedDate;
20use Stewart\Contracts\Exposure\ExposedDateTime;
21use Stewart\Contracts\Exposure\ExposedEntityKey;
22use Stewart\Contracts\Exposure\ExposedNumber;
23use Stewart\Contracts\Exposure\ExposedSelect;
24use Stewart\Contracts\Exposure\ExposedSensor;
25use Stewart\Contracts\Exposure\ExposedStateChange;
26use Stewart\Contracts\Exposure\ExposedSwitch;
27use Stewart\Contracts\Exposure\ExposedText;
28use Stewart\Contracts\Exposure\ExposedTime;
29use Stewart\Contracts\Exposure\ExposureConfig;
30use Stewart\Contracts\Exposure\NumberConfig;
31use Stewart\Contracts\Exposure\SelectConfig;
32use Stewart\Contracts\Exposure\SensorConfig;
33use Stewart\Contracts\Exposure\SwitchConfig;
34use Stewart\Contracts\Exposure\TextConfig;
35use Stewart\Contracts\Exposure\TimeConfig;
36use Stewart\Runtime\Model\ResourceScope;
37use Stewart\Runtime\Worker\Exposure\ExposureChannel;
38use Stewart\Runtime\Worker\Exposure\WorkerExposedAction;
39use Stewart\Runtime\Worker\Exposure\WorkerExposedBinarySensor;
40use Stewart\Runtime\Worker\Exposure\WorkerExposedButton;
41use Stewart\Runtime\Worker\Exposure\WorkerExposedDate;
42use Stewart\Runtime\Worker\Exposure\WorkerExposedDateTime;
43use Stewart\Runtime\Worker\Exposure\WorkerExposedNumber;
44use Stewart\Runtime\Worker\Exposure\WorkerExposedSelect;
45use Stewart\Runtime\Worker\Exposure\WorkerExposedSensor;
46use Stewart\Runtime\Worker\Exposure\WorkerExposedSwitch;
47use Stewart\Runtime\Worker\Exposure\WorkerExposedText;
48use Stewart\Runtime\Worker\Exposure\WorkerExposedTime;
49use Stewart\Runtime\Worker\Exposure\WorkerExposure;
50
51final readonly class WorkerEntityExposure implements EntityExposure
52{
53    public function __construct(
54        private ExposureChannel $channel,
55        private ResourceScope $resourceScope,
56    ) {}
57
58    public function forApp(AppId $appId): self
59    {
60        return new self($this->channel, ResourceScope::forApp($appId));
61    }
62
63    public function exposeSensor(ExposedEntityKey|string $key, SensorConfig $config = new SensorConfig(), ?DeviceInfo $device = null): ExposedSensor
64    {
65        return $this->exposeHandle(new WorkerExposedSensor($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
66    }
67
68    public function exposeBinarySensor(
69        ExposedEntityKey|string $key,
70        BinarySensorConfig $config = new BinarySensorConfig(),
71        ?DeviceInfo $device = null,
72    ): ExposedBinarySensor {
73        return $this->exposeHandle(new WorkerExposedBinarySensor($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
74    }
75
76    public function exposeSwitch(ExposedEntityKey|string $key, SwitchConfig $config = new SwitchConfig(), ?DeviceInfo $device = null): ExposedSwitch
77    {
78        return $this->exposeHandle(new WorkerExposedSwitch($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
79    }
80
81    public function exposeButton(ExposedEntityKey|string $key, ButtonConfig $config = new ButtonConfig(), ?DeviceInfo $device = null): ExposedButton
82    {
83        return $this->exposeHandle(new WorkerExposedButton($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
84    }
85
86    public function exposeNumber(ExposedEntityKey|string $key, NumberConfig $config, ?DeviceInfo $device = null): ExposedNumber
87    {
88        return $this->exposeHandle(new WorkerExposedNumber($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
89    }
90
91    public function exposeSelect(ExposedEntityKey|string $key, SelectConfig $config, ?DeviceInfo $device = null): ExposedSelect
92    {
93        return $this->exposeHandle(new WorkerExposedSelect($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
94    }
95
96    public function exposeText(ExposedEntityKey|string $key, TextConfig $config = new TextConfig(), ?DeviceInfo $device = null): ExposedText
97    {
98        return $this->exposeHandle(new WorkerExposedText($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
99    }
100
101    public function exposeTime(ExposedEntityKey|string $key, TimeConfig $config = new TimeConfig(), ?DeviceInfo $device = null): ExposedTime
102    {
103        return $this->exposeHandle(new WorkerExposedTime($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
104    }
105
106    public function exposeDate(ExposedEntityKey|string $key, DateConfig $config = new DateConfig(), ?DeviceInfo $device = null): ExposedDate
107    {
108        return $this->exposeHandle(new WorkerExposedDate($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
109    }
110
111    public function exposeDateTime(ExposedEntityKey|string $key, DateTimeConfig $config = new DateTimeConfig(), ?DeviceInfo $device = null): ExposedDateTime
112    {
113        return $this->exposeHandle(new WorkerExposedDateTime($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, $device);
114    }
115
116    public function exposeAction(ExposedEntityKey|string $key, ActionConfig $config = new ActionConfig()): ExposedAction
117    {
118        return $this->exposeHandle(new WorkerExposedAction($this->channel, $this->resourceScope, $this->claimKey($key), $config), $config, null);
119    }
120
121    /** @throws ExposureException */
122    private function claimKey(ExposedEntityKey|string $key): ExposedEntityKey
123    {
124        $key = ExposedEntityKey::fromKeyOrString($key);
125        $appId = $this->resourceScope->appId ?? throw ExposureException::outsideApp($key);
126
127        if ($this->channel->handles->isKeyTaken($this->resourceScope, $key)) {
128            throw ExposureException::keyTaken($appId, $key);
129        }
130
131        return $key;
132    }
133
134    /**
135     * @template THandle of WorkerExposure<covariant ExposureConfig>
136     *
137     * @param THandle $handle
138     * @return THandle
139     * @throws ExposureException
140     */
141    private function exposeHandle(WorkerExposure $handle, ExposureConfig $config, ?DeviceInfo $device): WorkerExposure
142    {
143        // Recorded before the broker answers, so a second exposure of the same key fails while the first is in flight.
144        $this->channel->handles->recordHandle($this->resourceScope, $handle->getKey(), $handle);
145
146        try {
147            $snapshot = $this->channel->requester->requestExposure($this->resourceScope, $handle->getKey(), $config, $device, new ExposedStateChange());
148        } catch (ExposureException $e) {
149            $this->channel->handles->forgetHandle($this->resourceScope, $handle->getKey());
150
151            throw $e;
152        }
153
154        if ($snapshot !== null) {
155            $handle->applySnapshot($snapshot);
156        }
157
158        return $handle;
159    }
160}