Lines 94.64% 53 / 56
Functions and Methods 66.66% 6 / 9
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ExposedEntityDefinition 94.64% 53 / 56 66.66% 6 / 9 24.09 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 fromConfig 92.85% 13 / 14 0.00% 0 / 1 5.01
 withConfigOf 0.00% 0 / 1 0.00% 0 / 1 2
 isAction 0.00% 0 / 1 0.00% 0 / 1 2
 toMessageFields 100.00% 1 / 1 100.00% 1 / 1 2
 listCommonFields 100.00% 6 / 6 100.00% 1 / 1 2
 listPlatformFields 100.00% 20 / 20 100.00% 1 / 1 9
 listActionFields 100.00% 11 / 11 100.00% 1 / 1 2
 dropNullFields 100.00% 1 / 1 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Client\Component;
6
7use LogicException;
8use Stewart\Contracts\Exposure\Action\ActionConfig;
9use Stewart\Contracts\Exposure\Action\ActionField;
10use Stewart\Contracts\Exposure\BinarySensorConfig;
11use Stewart\Contracts\Exposure\ButtonConfig;
12use Stewart\Contracts\Exposure\DateConfig;
13use Stewart\Contracts\Exposure\DateTimeConfig;
14use Stewart\Contracts\Exposure\DeviceInfo;
15use Stewart\Contracts\Exposure\ExposedEntityConfig;
16use Stewart\Contracts\Exposure\ExposedPlatform;
17use Stewart\Contracts\Exposure\ExposureConfig;
18use Stewart\Contracts\Exposure\NumberConfig;
19use Stewart\Contracts\Exposure\SelectConfig;
20use Stewart\Contracts\Exposure\SensorConfig;
21use Stewart\Contracts\Exposure\SwitchConfig;
22use Stewart\Contracts\Exposure\TextConfig;
23use Stewart\Contracts\Exposure\TimeConfig;
24
25final readonly class ExposedEntityDefinition
26{
27    /**
28     * @param array<string, mixed> $config
29     * @param array<string, string>|null $device
30     */
31    public function __construct(
32        public ExposedPlatform $platform,
33        public array $config,
34        public ?array $device = null,
35    ) {}
36
37    public static function fromConfig(ExposureConfig $config, ?DeviceInfo $device): self
38    {
39        $configFields = match (true) {
40            $config instanceof ExposedEntityConfig => [...self::listCommonFields($config), ...self::listPlatformFields($config)],
41            $config instanceof ActionConfig => self::listActionFields($config),
42            default => throw new LogicException(\sprintf('%s has no component encoding.', $config::class)),
43        };
44
45        return new self(
46            $config->getPlatform(),
47            self::dropNullFields($configFields),
48            $device === null ? null : self::dropNullFields([
49                'identifier' => $device->identifier,
50                'name' => $device->name,
51                'manufacturer' => $device->manufacturer,
52                'model' => $device->model,
53                'suggested_area' => $device->suggestedArea,
54            ]),
55        );
56    }
57
58    public function withConfigOf(self $other): self
59    {
60        return new self($other->platform, $other->config, $this->device);
61    }
62
63    public function isAction(): bool
64    {
65        return $this->platform === ExposedPlatform::Action;
66    }
67
68    /** @return array<string, mixed> */
69    public function toMessageFields(): array
70    {
71        return ['platform' => $this->platform->value, 'config' => $this->config, ...($this->device === null ? [] : ['device' => $this->device])];
72    }
73
74    /** @return array<string, mixed> */
75    private static function listCommonFields(ExposedEntityConfig $config): array
76    {
77        return [
78            'name' => $config->name,
79            'icon' => $config->icon,
80            'entity_category' => $config->entityCategory?->value,
81            'enabled_by_default' => $config->enabledByDefault ? null : false,
82        ];
83    }
84
85    /** @return array<string, mixed> */
86    private static function listPlatformFields(ExposedEntityConfig $config): array
87    {
88        return match (true) {
89            $config instanceof SensorConfig => [
90                'device_class' => $config->deviceClass?->value,
91                'unit_of_measurement' => $config->unit,
92                'state_class' => $config->stateClass?->value,
93                'suggested_display_precision' => $config->displayPrecision,
94                'options' => $config->options === [] ? null : $config->options,
95            ],
96            $config instanceof BinarySensorConfig, $config instanceof SwitchConfig, $config instanceof ButtonConfig => ['device_class' => $config->deviceClass?->value],
97            $config instanceof NumberConfig => [
98                'min' => $config->min,
99                'max' => $config->max,
100                'step' => $config->step,
101                'mode' => $config->mode->value,
102                'device_class' => $config->deviceClass?->value,
103                'unit_of_measurement' => $config->unit,
104            ],
105            $config instanceof SelectConfig => ['options' => $config->options],
106            $config instanceof TextConfig => ['min' => $config->min, 'max' => $config->max, 'pattern' => $config->pattern, 'mode' => $config->mode->value],
107            $config instanceof TimeConfig, $config instanceof DateConfig, $config instanceof DateTimeConfig => [],
108            default => throw new LogicException(\sprintf('%s has no component encoding.', $config::class)),
109        };
110    }
111
112    /** @return array<string, mixed> */
113    private static function listActionFields(ActionConfig $config): array
114    {
115        return [
116            'name' => $config->name,
117            'description' => $config->description,
118            'fields' => array_map(static fn(ActionField $field): array => self::dropNullFields([
119                'name' => $field->name,
120                'type' => $field->type->value,
121                'required' => $field->required,
122                'description' => $field->description,
123                'options' => $field->options === [] ? null : $field->options,
124            ]), $config->fields),
125        ];
126    }
127
128    /**
129     * @template T
130     *
131     * @param array<string, T|null> $fields
132     * @return array<string, T>
133     */
134    private static function dropNullFields(array $fields): array
135    {
136        return array_filter($fields, static fn(mixed $value): bool => $value !== null);
137    }
138}