Lines 100.00% 8 / 8
Functions and Methods 100.00% 6 / 6
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
Keyspace 100.00% 8 / 8 100.00% 6 / 6 7 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 forApp 100.00% 1 / 1 100.00% 1 / 1 1
 forGlobalScope 100.00% 1 / 1 100.00% 1 / 1 1
 forRuntime 100.00% 1 / 1 100.00% 1 / 1 1
 buildFullKey 100.00% 3 / 3 100.00% 1 / 1 2
 extractRelativeKey 100.00% 1 / 1 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Store;
6
7use Stewart\Contracts\App\AppId;
8use Stewart\Contracts\Exception\StoreError;
9use Stewart\Contracts\Exception\StoreException;
10
11final readonly class Keyspace
12{
13    // Glob-safe for prefix scans; \z rejects a trailing newline.
14    private const string KEY_PATTERN = '/\A[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\z/';
15
16    private function __construct(public string $prefix) {}
17
18    public static function forApp(StorePrefix $prefix, AppId $appId): self
19    {
20        return new self($prefix->value . ':app:' . $appId->value . ':');
21    }
22
23    public static function forGlobalScope(StorePrefix $prefix): self
24    {
25        return new self($prefix->value . ':global:');
26    }
27
28    public static function forRuntime(StorePrefix $prefix): self
29    {
30        return new self($prefix->value . ':runtime:');
31    }
32
33    /** @throws StoreException */
34    public function buildFullKey(string $key): string
35    {
36        if (preg_match(self::KEY_PATTERN, $key) !== 1) {
37            throw StoreException::createForReason(StoreError::KeyInvalid, key: $key);
38        }
39
40        return $this->prefix . $key;
41    }
42
43    public function extractRelativeKey(string $fullKey): string
44    {
45        return substr($fullKey, \strlen($this->prefix));
46    }
47}