Lines 100.00% 42 / 42
Methods 100.00% 5 / 5
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 parseServicesResponse 100.00% 11 / 11 100.00% 1 / 1 3
 parseDefinition 100.00% 9 / 9 100.00% 1 / 1 1
 flattenFields 100.00% 8 / 8 100.00% 1 / 1 3
 parseField 100.00% 12 / 12 100.00% 1 / 1 1
 sortRequiredFirstThenByName 100.00% 2 / 2 100.00% 1 / 1 1
10final readonly class ServiceCatalogParser
11{
12    /** @param array<string, mixed> $raw */
13    public function parseServicesResponse(array $raw): ServiceCatalog
14    {
15        $byDomain = [];
16
17        $response = RawMap::fromValue($raw);
18
19        foreach ($response->listKeys() as $domain) {
20            $services = $response->readMap($domain);
21            $definitions = [];
22
23            foreach ($services->listKeys() as $name) {
24                $definitions[$name] = $this->parseDefinition($domain, $name, $services->readMap($name));
25            }
26
27            ksort($definitions);
28            $byDomain[$domain] = ServiceDefinitionCollection::fromList($definitions);
29        }
30
31        ksort($byDomain);
32
33        return new ServiceCatalog($byDomain);
34    }
35
36    private function parseDefinition(string $domain, string $name, RawMap $service): ServiceDefinition
37    {
38        return new ServiceDefinition(
39            domain: $domain,
40            name: $name,
41            title: $service->readString('name'),
42            description: $service->readString('description'),
43            fields: $this->sortRequiredFirstThenByName($this->flattenFields($service->readMap('fields'))),
44            target: ServiceTargetSpec::fromRaw($service),
45            returnsResponse: $service->hasKey('response'),
46        );
47    }
48
49    /** @return list<ServiceField> */
50    private function flattenFields(RawMap $fields): array
51    {
52        $flat = [];
53
54        foreach ($fields->listKeys() as $name) {
55            $field = $fields->readMap($name);
56
57            if ($field->hasKey('fields')) {
58                $flat = [...$flat, ...$this->flattenFields($field->readMap('fields'))];
59
60                continue;
61            }
62
63            $flat[] = $this->parseField($name, $field);
64        }
65
66        return $flat;
67    }
68
69    private function parseField(string $name, RawMap $field): ServiceField
70    {
71        $selector = new ServiceSelector($field->readMap('selector'));
72        $configuration = $selector->configuration;
73
74        return new ServiceField(
75            name: $name,
76            type: $selector->resolvePhpType(),
77            required: $field->readBool('required'),
78            label: $field->readString('name'),
79            description: $field->readString('description'),
80            unit: $configuration->readString('unit_of_measurement'),
81            min: $configuration->readNumber('min'),
82            max: $configuration->readNumber('max'),
83        );
84    }
85
86    /**
87     * @param list<ServiceField> $fields
88     * @return list<ServiceField>
89     */
90    private function sortRequiredFirstThenByName(array $fields): array
91    {
92        usort($fields, static fn(ServiceField $a, ServiceField $b): int => [!$a->required, $a->name] <=> [!$b->required, $b->name]);
93
94        return $fields;
95    }
96}