Lines 100.00% 18 / 18
Functions and Methods 100.00% 5 / 5
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
MqttServerUrl 100.00% 18 / 18 100.00% 5 / 5 12 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 parse 100.00% 14 / 14 100.00% 1 / 1 8
 revealPassword 100.00% 1 / 1 100.00% 1 / 1 1
 toSocketUri 100.00% 1 / 1 100.00% 1 / 1 1
 __toString 100.00% 1 / 1 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Config;
6
7use SensitiveParameter;
8use Stewart\Runtime\Exception\ConfigurationException;
9use Stewart\Support\Url\UrlRedactor;
10use Stringable;
11
12final readonly class MqttServerUrl implements Stringable
13{
14    private const string PLAIN_SCHEME = 'mqtt';
15
16    private const string TLS_SCHEME = 'mqtts';
17
18    private const int PLAIN_DEFAULT_PORT = 1883;
19
20    private const int TLS_DEFAULT_PORT = 8883;
21
22    private function __construct(
23        public bool $usesTls,
24        public string $host,
25        public int $port,
26        public ?string $username,
27        #[SensitiveParameter]
28        private ?string $password,
29        private string $redacted,
30    ) {}
31
32    /** @throws ConfigurationException */
33    public static function parse(#[SensitiveParameter] string $url): self
34    {
35        $redacted = UrlRedactor::redactCredentials($url);
36        $parts = parse_url($url);
37        $scheme = \is_array($parts) ? strtolower($parts['scheme'] ?? '') : '';
38
39        if (!\is_array($parts) || !isset($parts['host']) || !\in_array($scheme, [self::PLAIN_SCHEME, self::TLS_SCHEME], true)) {
40            throw ConfigurationException::valueInvalid($redacted, 'an MQTT server URL; use mqtt://host:port or mqtts://host:port');
41        }
42
43        $usesTls = $scheme === self::TLS_SCHEME;
44
45        return new self(
46            usesTls: $usesTls,
47            host: $parts['host'],
48            port: $parts['port'] ?? ($usesTls ? self::TLS_DEFAULT_PORT : self::PLAIN_DEFAULT_PORT),
49            username: isset($parts['user']) ? rawurldecode($parts['user']) : null,
50            password: isset($parts['pass']) ? rawurldecode($parts['pass']) : null,
51            redacted: $redacted,
52        );
53    }
54
55    public function revealPassword(): ?string
56    {
57        return $this->password;
58    }
59
60    public function toSocketUri(): string
61    {
62        return 'tcp://' . $this->host . ':' . $this->port;
63    }
64
65    public function __toString(): string
66    {
67        return $this->redacted;
68    }
69}