Lines 96.66% 29 / 30
Functions and Methods 87.50% 7 / 8
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
AppPauseOverrideStore 96.66% 29 / 30 87.50% 7 / 8 16 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 loadOverrides 100.00% 9 / 9 100.00% 1 / 1 4
 hasOverride 100.00% 2 / 2 100.00% 1 / 1 2
 saveOverride 100.00% 1 / 1 100.00% 1 / 1 1
 removeOverride 100.00% 1 / 1 100.00% 1 / 1 1
 runWrite 100.00% 8 / 8 100.00% 1 / 1 3
 buildOverrideKey 100.00% 1 / 1 100.00% 1 / 1 1
 readOverride 85.71% 6 / 7 0.00% 0 / 1 3.03
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Broker;
6
7use Closure;
8use JsonException;
9use Psr\Log\LoggerInterface;
10use Stewart\Contracts\App\AppId;
11use Stewart\Contracts\Exception\StewartException;
12use Stewart\Contracts\Exception\StoreException;
13use Stewart\Runtime\App\AppPauseOverride;
14use Stewart\Runtime\App\Collection\AppPauseOverrideCollection;
15use Stewart\Runtime\Config\PersistenceConfig;
16use Stewart\Runtime\Lifecycle\AppPauseOverridePersistence;
17use Stewart\Store\Keyspace;
18use Stewart\Store\StoreBackend;
19
20final readonly class AppPauseOverrideStore
21{
22    private const string KEY_STEM = 'app-pause:';
23
24    private Keyspace $keyspace;
25
26    public function __construct(
27        private AppPauseOverrideCodec $codec,
28        private LoggerInterface $logger,
29        PersistenceConfig $persistenceConfig,
30        private ?StoreBackend $store = null,
31    ) {
32        $this->keyspace = Keyspace::forRuntime($persistenceConfig->prefix);
33    }
34
35    /** @throws StoreException */
36    public function loadOverrides(): AppPauseOverrideCollection
37    {
38        $store = $this->store;
39
40        if ($store === null) {
41            return AppPauseOverrideCollection::keyedByAppId([]);
42        }
43
44        $overrides = [];
45
46        foreach ($store->keysWithPrefix($this->keyspace->prefix . self::KEY_STEM) as $fullKey) {
47            $override = $this->readOverride($store, $fullKey);
48
49            if ($override !== null) {
50                $overrides[] = $override;
51            }
52        }
53
54        return AppPauseOverrideCollection::keyedByAppId($overrides);
55    }
56
57    /** @throws StoreException */
58    public function hasOverride(AppId $appId): bool
59    {
60        $store = $this->store;
61
62        return $store !== null && $store->exists($this->buildOverrideKey($appId));
63    }
64
65    public function saveOverride(AppPauseOverride $override): AppPauseOverridePersistence
66    {
67        return $this->runWrite($override->appId, fn(StoreBackend $store, string $fullKey) => $store->write($fullKey, $this->codec->encodeOverride($override), null));
68    }
69
70    public function removeOverride(AppId $appId): AppPauseOverridePersistence
71    {
72        return $this->runWrite($appId, static fn(StoreBackend $store, string $fullKey) => $store->remove($fullKey));
73    }
74
75    /** @param Closure(StoreBackend, string): void $write */
76    private function runWrite(AppId $appId, Closure $write): AppPauseOverridePersistence
77    {
78        $store = $this->store;
79
80        if ($store === null) {
81            return AppPauseOverridePersistence::NotConfigured;
82        }
83
84        try {
85            $write($store, $this->buildOverrideKey($appId));
86        } catch (JsonException|StewartException $e) {
87            $this->logger->warning('Could not store the pause override', ['app' => $appId->value, 'exception' => $e]);
88
89            return AppPauseOverridePersistence::Failed;
90        }
91
92        return AppPauseOverridePersistence::Stored;
93    }
94
95    /** @throws StoreException */
96    private function buildOverrideKey(AppId $appId): string
97    {
98        return $this->keyspace->buildFullKey(self::KEY_STEM . $appId->value);
99    }
100
101    /** @throws StoreException */
102    private function readOverride(StoreBackend $store, string $fullKey): ?AppPauseOverride
103    {
104        $json = $store->read($fullKey);
105
106        if ($json === null) {
107            return null;
108        }
109
110        try {
111            return $this->codec->decodeOverride(new AppId(substr($this->keyspace->extractRelativeKey($fullKey), \strlen(self::KEY_STEM))), $json);
112        } catch (JsonException|StewartException $e) {
113            $this->logger->warning('Ignoring an unreadable pause override', ['key' => $fullKey, 'exception' => $e]);
114
115            return null;
116        }
117    }
118}