Lines 100.00% 21 / 21
Methods 100.00% 6 / 6
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 open 100.00% 1 / 1 100.00% 1 / 1 1
 send 100.00% 8 / 8 100.00% 1 / 1 3
 receive 100.00% 8 / 8 100.00% 1 / 1 3
 close 100.00% 2 / 2 100.00% 1 / 1 1
 closedHere 100.00% 1 / 1 100.00% 1 / 1 1
18final class Connection
19{
20    private const string TERMINATOR = "\0";
21
22    private readonly IO\Reader $reader;
23
24    private ?string $closedBecause = null;
25
26    private bool $closedHere = false;
27
28    public function __construct(
29        private readonly TCP\StreamInterface $stream,
30    ) {
31        $this->reader = new IO\Reader($stream);
32    }
33
34    public static function open(Server $server): self
35    {
36        return new self(TCP\connect($server->address->host, $server->address->port ?? 0));
37    }
38
39    /** @throws PeerClosedException */
40    public function send(Command $command): void
41    {
42        if ($this->closedBecause !== null) {
43            throw new PeerClosedException($this->closedBecause);
44        }
45
46        try {
47            $this->stream->writeAll($command->toMessage() . self::TERMINATOR);
48        } catch (IO\Exception\RuntimeException $exception) {
49            throw new PeerClosedException(
50                'Connection error while sending: ' . $exception->getMessage(),
51                previous: $exception,
52            );
53        }
54    }
55
56    /**
57     * @throws PeerClosedException
58     *
59     * @return Option\Option<Message>
60     */
61    public function receive(CancellationTokenInterface $cancellation = new NullCancellationToken()): Option\Option
62    {
63        try {
64            $raw = $this->reader->readUntil(self::TERMINATOR, $cancellation);
65        } catch (IO\Exception\RuntimeException $exception) {
66            $this->closedBecause = 'Connection error while receiving: ' . $exception->getMessage();
67
68            throw new PeerClosedException($this->closedBecause, previous: $exception);
69        }
70
71        if ($raw === null) {
72            $this->closedBecause = 'The server closed the connection.';
73
74            throw new PeerClosedException($this->closedBecause);
75        }
76
77        return MessageParser::parse($raw);
78    }
79
80    public function close(): void
81    {
82        $this->closedHere = true;
83        $this->stream->close();
84    }
85
86    public function closedHere(): bool
87    {
88        return $this->closedHere;
89    }
90}