diff --git a/CHANGELOG.md b/CHANGELOG.md index 2851cdf..570b53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ ### Changed - Requires `innmind/immutable:~5.2` -- Requires `innmind/operating-system:~4.1` +- Requires `innmind/operating-system:~5.0` - Requires `innmind/filesystem:~7.0` -- Requires `innmind/io:~2.3` +- Requires `innmind/io:~2.6` +- Carried state inside a `Innmind\AMQP\Command` is now wrapped inside a `Innmind\AMQP\Client\State` +- `Innmind\AMQP\Client::of()` now requires an instance of `Innmind\OperatingSystem\Filesystem` as a second argument ## 4.3.0 - 2023-09-23 diff --git a/README.md b/README.md index c634188..4602cb6 100644 --- a/README.md +++ b/README.md @@ -110,10 +110,10 @@ Feel free to look at the `Command` namespace to explore all capabilities. make benchmark Publishing 4000 msgs with 1KB of content: php benchmark/producer.php 4000 -0.39038109779358 +0.48978996276855 Consuming 4000: php benchmark/consumer.php -Pid: 701, Count: 4000, Time: 1.6017 +Pid: 701, Count: 4000, Time: 2.3580 ``` By comparison, the `php-amqplib` produces this result: diff --git a/codecov.yml b/codecov.yml index a80c7a2..88d5189 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,4 @@ ignore: - benchmark - fixtures + - .php-cs-fixer.dist.php diff --git a/composer.json b/composer.json index 318e7dd..d74d4c2 100644 --- a/composer.json +++ b/composer.json @@ -21,11 +21,11 @@ "innmind/math": "~6.0", "innmind/url": "~4.1", "ramsey/uuid": "~4.0", - "innmind/operating-system": "~4.1", + "innmind/operating-system": "~5.0", "innmind/media-type": "~2.0", "innmind/filesystem": "~7.0", "innmind/stream": "~4.0", - "innmind/io": "~2.3" + "innmind/io": "~2.6" }, "autoload": { "psr-4": { diff --git a/docs/Publish many messages.md b/docs/Publish many messages.md index 82877ae..bd03a36 100644 --- a/docs/Publish many messages.md +++ b/docs/Publish many messages.md @@ -43,7 +43,7 @@ $client = Factory::of($os)->make(/* details */); $os ->filesystem() ->mount(Path::of('/path/to/some/directory/')) - ->get(new Name('leads.csv')) + ->get(Name::of('leads.csv')) ->map( static fn($file) => $file ->content() diff --git a/src/Client.php b/src/Client.php index 9c0a54b..1a4afc8 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,10 +10,9 @@ Transport\Frame\Method, Model\Channel\Close as CloseChannel, }; -use Innmind\OperatingSystem\CurrentProcess; -use Innmind\Stream\{ - Capabilities, - Streams, +use Innmind\OperatingSystem\{ + CurrentProcess, + Filesystem, }; use Innmind\Immutable\{ Either, @@ -27,7 +26,7 @@ final class Client private Maybe $command; /** @var callable(): Maybe */ private $load; - private Capabilities $streams; + private Filesystem $filesystem; /** @var Maybe */ private Maybe $signals; @@ -39,19 +38,19 @@ final class Client private function __construct( Maybe $command, callable $load, - Capabilities $streams, + Filesystem $filesystem, Maybe $signals, ) { $this->command = $command; $this->load = $load; - $this->streams = $streams; + $this->filesystem = $filesystem; $this->signals = $signals; } /** * @param callable(): Maybe $load */ - public static function of(callable $load, Capabilities $streams = null): self + public static function of(callable $load, Filesystem $filesystem): self { /** @var Maybe */ $command = Maybe::nothing(); @@ -61,7 +60,7 @@ public static function of(callable $load, Capabilities $streams = null): self return new self( $command, $load, - $streams ?? Streams::fromAmbientAuthority(), + $filesystem, $signals, ); } @@ -74,22 +73,17 @@ public function with(Command $command): self ->map(static fn($previous) => new Command\Pipe($previous, $command)) ->otherwise(static fn() => Maybe::just($command)), $this->load, - $this->streams, + $this->filesystem, $this->signals, ); } public function listenSignals(CurrentProcess $currentProcess): self { - // We ask for the current process instead of the signals wrapper directly - // because the user may fork the process between the time this method is - // called and the time the listeners are installed (when run is called). - // This would result on the listeners being installed for the parent - // process instead of the child. return new self( $this->command, $this->load, - $this->streams, + $this->filesystem, Maybe::just($currentProcess), ); } @@ -108,12 +102,12 @@ public function run(mixed $state): Either ->openChannel() ->flatMap(function($in) use ($command, $state) { [$connection, $channel] = $in; - $read = MessageReader::of($this->streams); + $read = MessageReader::of($this->filesystem); - return $command($connection, $channel, $read, $state)->flatMap( - fn($clientState) => $this - ->close($clientState->connection(), $channel) - ->map(static fn(): mixed => $clientState->userState()), + return $command($connection, $channel, $read, Client\State::of($state))->flatMap( + fn($state) => $this + ->close($connection, $channel) + ->map(static fn(): mixed => $state->unwrap()), ); }), static fn() => Either::right($state), @@ -134,21 +128,20 @@ private function openChannel(): Either return ($this->load)() ->either() ->leftMap(static fn() => Failure::toOpenConnection()) - ->map(static fn($connection) => $connection->send( - static fn($protocol) => $protocol->channel()->open($channel), - )) - ->map(static fn($continuation) => $continuation->wait(Method::channelOpenOk)) ->flatMap( - fn($continuation) => $continuation - ->connection() - ->map(fn($connection) => $this->signals->match( + fn($connection) => $connection + ->request( + static fn($protocol) => $protocol->channel()->open($channel), + Method::channelOpenOk, + ) + ->map(fn() => $this->signals->match( static fn($process) => $connection->listenSignals( $process->signals(), $channel, ), - static fn() => $connection, + static fn() => null, )) - ->map(static fn($connection) => [$connection, $channel]) + ->map(static fn() => [$connection, $channel]) ->leftMap(static fn() => Failure::toOpenChannel()), ); } @@ -160,15 +153,16 @@ private function close(Connection $connection, Channel $channel): Either { /** @var Either */ return $connection - ->send(static fn($protocol) => $protocol->channel()->close( - $channel, - CloseChannel::demand(), - )) - ->wait(Method::channelCloseOk) - ->connection() + ->request( + static fn($protocol) => $protocol->channel()->close( + $channel, + CloseChannel::demand(), + ), + Method::channelCloseOk, + ) ->leftMap(static fn() => Failure::toCloseChannel()) ->flatMap( - static fn($connection) => $connection + static fn() => $connection ->close() ->either() ->leftMap(static fn() => Failure::toCloseConnection()), diff --git a/src/Client/State.php b/src/Client/State.php index 1c16bd8..c73ec6a 100644 --- a/src/Client/State.php +++ b/src/Client/State.php @@ -3,34 +3,22 @@ namespace Innmind\AMQP\Client; -use Innmind\AMQP\Transport\Connection; - -/** - * @internal - */ final class State { - private Connection $connection; - private mixed $userState; - - private function __construct(Connection $connection, mixed $userState) - { - $this->connection = $connection; - $this->userState = $userState; - } + private mixed $value; - public static function of(Connection $connection, mixed $userState): self + private function __construct(mixed $value) { - return new self($connection, $userState); + $this->value = $value; } - public function connection(): Connection + public static function of(mixed $value): self { - return $this->connection; + return new self($value); } - public function userState(): mixed + public function unwrap(): mixed { - return $this->userState; + return $this->value; } } diff --git a/src/Command.php b/src/Command.php index 3076c2e..e1fb1f9 100644 --- a/src/Command.php +++ b/src/Command.php @@ -19,6 +19,6 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + Client\State $state, ): Either; } diff --git a/src/Command/Bind.php b/src/Command/Bind.php index 8ce2780..c677241 100644 --- a/src/Command/Bind.php +++ b/src/Command/Bind.php @@ -9,11 +9,15 @@ Failure, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Method, Model\Queue\Binding, }; -use Innmind\Immutable\Either; +use Innmind\Immutable\{ + Either, + Sequence, +}; final class Bind implements Command { @@ -28,17 +32,20 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->queue()->bind( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::queueBindOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state)) + $frames = fn(Protocol $protocol): Sequence => $protocol->queue()->bind( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection->request($frames, Method::queueBindOk), + false => $connection->send($frames), + }; + + return $sideEffect + ->map(static fn() => $state) ->leftMap(fn() => Failure::toBind($this->command)); } diff --git a/src/Command/Consume.php b/src/Command/Consume.php index c11cf24..a2789f5 100644 --- a/src/Command/Consume.php +++ b/src/Command/Consume.php @@ -9,6 +9,7 @@ Client\State, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame, Transport\Frame\Channel, Transport\Frame\Value, @@ -22,6 +23,7 @@ use Innmind\Immutable\{ Maybe, Either, + Sequence, Predicate\Instance, }; @@ -44,26 +46,31 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->basic()->consume( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::basicConsumeOk) - ->then( - fn($connection, $frame) => $this->maybeStart( + $frames = fn(Protocol $protocol): Sequence => $protocol->basic()->consume( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection + ->request($frames, Method::basicConsumeOk) + ->flatMap(fn($frame) => $this->maybeStart( $connection, $channel, $read, $frame, $state, - ), - static fn($connection) => State::of($connection, $state), // this case should not happen - ) - ->leftMap(fn() => Failure::toConsume($this->command)); + )), + false => $connection + ->send($frames) + ->map(static fn() => $state), + }; + + return $sideEffect->leftMap( + fn() => Failure::toConsume($this->command), + ); } public static function of(string $queue): self @@ -90,7 +97,7 @@ private function maybeStart( Channel $channel, MessageReader $read, Frame $frame, - mixed $state, + State $state, ): Either { return $frame ->values() @@ -115,11 +122,11 @@ private function start( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, string $consumerTag, ): Either { /** @var Either */ - $consumed = Either::right(State::of($connection, $state)); + $consumed = Either::right($state); // here the best approach would be to use recursion to avoid unwrapping // the monads but it would end up with a too deep call stack for inifite // consumers as each new message would mean a new function call in the @@ -132,9 +139,9 @@ private function start( default => $state, }) ->flatMap(fn($state) => $this->waitDeliver( - $state->connection(), + $connection, $channel, - $state->userState(), + $state, $consumerTag, $read, )); @@ -155,7 +162,7 @@ private function start( private function waitDeliver( Connection $connection, Channel $channel, - mixed $state, + State $state, string $consumerTag, MessageReader $read, ): Either { @@ -163,15 +170,15 @@ private function waitDeliver( return $connection ->wait(Method::basicDeliver) ->flatMap( - fn($receivedFrame) => $read($receivedFrame->connection())->flatMap( - fn($receivedMessage) => $this->maybeConsume( - $receivedMessage->connection(), + fn($received) => $read($connection)->flatMap( + fn($message) => $this->maybeConsume( + $connection, $channel, $read, $state, $consumerTag, - $receivedFrame->frame(), - $receivedMessage->message(), + $received->frame(), + $message, ), ), ) @@ -185,7 +192,7 @@ private function maybeConsume( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, string $consumerTag, Frame $frame, Message $message, @@ -242,13 +249,13 @@ private function consume( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, Details $details, Message $message, string $consumerTag, ): Either { return ($this->consume)( - $state, + $state->unwrap(), $message, Continuation::of($state), $details, diff --git a/src/Command/DeclareExchange.php b/src/Command/DeclareExchange.php index 5288ef5..06f18db 100644 --- a/src/Command/DeclareExchange.php +++ b/src/Command/DeclareExchange.php @@ -10,11 +10,15 @@ Model\Exchange\Type, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Method, Failure, }; -use Innmind\Immutable\Either; +use Innmind\Immutable\{ + Either, + Sequence, +}; final class DeclareExchange implements Command { @@ -29,17 +33,20 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->exchange()->declare( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::exchangeDeclareOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state)) + $frames = fn(Protocol $protocol): Sequence => $protocol->exchange()->declare( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection->request($frames, Method::exchangeDeclareOk), + false => $connection->send($frames), + }; + + return $sideEffect + ->map(static fn() => $state) ->leftMap(fn() => Failure::toDeclareExchange($this->command)); } diff --git a/src/Command/DeclareQueue.php b/src/Command/DeclareQueue.php index 5a7b42a..8bb3281 100644 --- a/src/Command/DeclareQueue.php +++ b/src/Command/DeclareQueue.php @@ -11,6 +11,7 @@ Model\Count, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Method, Transport\Frame\Value, @@ -19,6 +20,7 @@ use Innmind\Immutable\{ Maybe, Either, + Sequence, Predicate\Instance, }; @@ -35,17 +37,17 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->queue()->declare( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::queueDeclareOk) - ->then( - function($connection, $frame) use ($state) { + $frames = fn(Protocol $protocol): Sequence => $protocol->queue()->declare( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection + ->request($frames, Method::queueDeclareOk) + ->flatMap(static function($frame) { $name = $frame ->values() ->first() @@ -68,12 +70,14 @@ function($connection, $frame) use ($state) { // maybe in the future we could expose this info to the user return Maybe::all($name, $message, $consumer) ->map(DeclareOk::of(...)) - ->map(static fn() => State::of($connection, $state)) - ->either() - ->leftMap(fn() => Failure::toDeclareQueue($this->command)); - }, - static fn($connection) => State::of($connection, $state), - ); + ->either(); + }), + false => $connection->send($frames), + }; + + return $sideEffect + ->map(static fn() => $state) + ->leftMap(fn() => Failure::toDeclareQueue($this->command)); } public static function of(string $name): self diff --git a/src/Command/DeleteExchange.php b/src/Command/DeleteExchange.php index e75f544..8ab9866 100644 --- a/src/Command/DeleteExchange.php +++ b/src/Command/DeleteExchange.php @@ -9,11 +9,15 @@ Model\Exchange\Deletion, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Method, Failure, }; -use Innmind\Immutable\Either; +use Innmind\Immutable\{ + Either, + Sequence, +}; final class DeleteExchange implements Command { @@ -28,17 +32,20 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->exchange()->delete( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::exchangeDeleteOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state)) + $frames = fn(Protocol $protocol): Sequence => $protocol->exchange()->delete( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection->request($frames, Method::exchangeDeleteOk), + false => $connection->send($frames), + }; + + return $sideEffect + ->map(static fn() => $state) ->leftMap(fn() => Failure::toDeleteExchange($this->command)); } diff --git a/src/Command/DeleteQueue.php b/src/Command/DeleteQueue.php index 392ef6c..61025c2 100644 --- a/src/Command/DeleteQueue.php +++ b/src/Command/DeleteQueue.php @@ -11,14 +11,15 @@ Model\Count, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Method, Transport\Frame\Value, Failure, }; use Innmind\Immutable\{ - Maybe, Either, + Sequence, Predicate\Instance, }; @@ -35,30 +36,34 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->queue()->delete( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::queueDeleteOk) - ->then( - // this is here just to make sure the response is valid maybe in - // the future we could expose this info to the user - fn($connection, $frame) => $frame - ->values() - ->first() - ->keep(Instance::of(Value\UnsignedLongInteger::class)) - ->map(static fn($value) => $value->original()) - ->map(Count::of(...)) - ->map(DeleteOk::of(...)) - ->map(static fn() => State::of($connection, $state)) - ->either() - ->leftMap(fn() => Failure::toDeleteQueue($this->command)), - static fn($connection) => State::of($connection, $state), - ); + $frames = fn(Protocol $protocol): Sequence => $protocol->queue()->delete( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection + ->request($frames, Method::queueDeleteOk) + ->flatMap( + // this is here just to make sure the response is valid + // maybe in the future we could expose this info to the user + static fn($frame) => $frame + ->values() + ->first() + ->keep(Instance::of(Value\UnsignedLongInteger::class)) + ->map(static fn($value) => $value->original()) + ->map(Count::of(...)) + ->map(DeleteOk::of(...)) + ->either(), + ), + false => $connection->send($frames), + }; + + return $sideEffect + ->map(static fn() => $state) + ->leftMap(fn() => Failure::toDeleteQueue($this->command)); } public static function of(string $name): self diff --git a/src/Command/Get.php b/src/Command/Get.php index ad569b5..b732432 100644 --- a/src/Command/Get.php +++ b/src/Command/Get.php @@ -50,20 +50,20 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { /** * @psalm-suppress MixedArgumentTypeCoercion * @var Either */ return Sequence::of(...\array_fill(0, $this->take, null))->reduce( - Either::right(State::of($connection, $state)), + Either::right($state), fn(Either $state) => $state->flatMap( fn(State $state) => $this->doGet( - $state->connection(), + $connection, $channel, $read, - $state->userState(), + $state, ), ), ); @@ -101,24 +101,26 @@ public function doGet( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { /** @var Either */ return $connection - ->send(fn($protocol) => $protocol->basic()->get( - $channel, - $this->command, - )) - ->wait(Method::basicGetOk, Method::basicGetEmpty) - ->then( - fn($connection, $frame) => $this->maybeConsume( + ->request( + fn($protocol) => $protocol->basic()->get( + $channel, + $this->command, + ), + Method::basicGetOk, + Method::basicGetEmpty, + ) + ->flatMap( + fn($frame) => $this->maybeConsume( $connection, $channel, $read, $frame, $state, ), - static fn($connection) => State::of($connection, $state), // this case should not happen ) ->leftMap(fn() => Failure::toGet($this->command)); } @@ -131,11 +133,11 @@ private function maybeConsume( Channel $channel, MessageReader $read, Frame $frame, - mixed $state, + State $state, ): Either { if ($frame->is(Method::basicGetEmpty)) { /** @var Either */ - return Either::right(State::of($connection, $state)); + return Either::right($state); } $deliveryTag = $frame @@ -172,12 +174,12 @@ private function maybeConsume( ->leftMap(fn() => Failure::toGet($this->command)) ->flatMap( fn($details) => $read($connection)->flatMap( - fn($received) => $this->consume( - $received->connection(), + fn($message) => $this->consume( + $connection, $channel, $read, $state, - $received->message(), + $message, $details, ), ), @@ -191,12 +193,12 @@ private function consume( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, Message $message, Details $details, ): Either { return ($this->consume)( - $state, + $state->unwrap(), $message, Continuation::of($state), $details, diff --git a/src/Command/Pipe.php b/src/Command/Pipe.php index b406bc5..2a0bb75 100644 --- a/src/Command/Pipe.php +++ b/src/Command/Pipe.php @@ -5,10 +5,10 @@ use Innmind\AMQP\{ Command, - Client\State, Transport\Connection, Transport\Connection\MessageReader, Transport\Frame\Channel, + Client\State, }; use Innmind\Immutable\Either; @@ -30,14 +30,14 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { return ($this->first)($connection, $channel, $read, $state)->flatMap( fn($state) => ($this->second)( - $state->connection(), + $connection, $channel, $read, - $state->userState(), + $state, ), ); } diff --git a/src/Command/Publish.php b/src/Command/Publish.php index 86fcc7e..f05a8d2 100644 --- a/src/Command/Publish.php +++ b/src/Command/Publish.php @@ -16,6 +16,7 @@ use Innmind\Immutable\{ Either, Sequence, + SideEffect, }; final class Publish implements Command @@ -35,21 +36,18 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** - * @psalm-suppress MixedArgumentTypeCoercion - * @var Either - */ + /** @var Either */ return $this ->commands ->reduce( - Either::right($connection), - fn(Either $connection, $command) => $connection->flatMap( - fn(Connection $connection) => $this->publish($connection, $channel, $command), + Either::right(new SideEffect), + fn(Either $state, $command) => $state->flatMap( + fn() => $this->publish($connection, $channel, $command), ), ) - ->map(static fn($connection) => State::of($connection, $state)); + ->map(static fn() => $state); } public static function one(Message $message): self @@ -80,21 +78,19 @@ public function withRoutingKey(string $routingKey): self } /** - * @return Either + * @return Either */ private function publish( Connection $connection, Channel $channel, Model $command, ): Either { - /** @var Either */ return $connection ->send(static fn($protocol, $maxFrameSize) => $protocol->basic()->publish( $channel, $command, $maxFrameSize, )) - ->connection() ->leftMap(static fn() => Failure::toPublish($command)); } } diff --git a/src/Command/Purge.php b/src/Command/Purge.php index c12bd0c..bc4b023 100644 --- a/src/Command/Purge.php +++ b/src/Command/Purge.php @@ -9,6 +9,7 @@ Client\State, Transport\Connection, Transport\Connection\MessageReader, + Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Method, Transport\Frame\Value, @@ -18,6 +19,7 @@ }; use Innmind\Immutable\{ Either, + Sequence, Predicate\Instance, }; @@ -34,30 +36,34 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ - return $connection - ->send(fn($protocol) => $protocol->queue()->purge( - $channel, - $this->command, - )) - ->maybeWait($this->command->shouldWait(), Method::queuePurgeOk) - ->then( - // this is here just to make sure the response is valid maybe in - // the future we could expose this info to the user - fn($connection, $frame) => $frame - ->values() - ->first() - ->keep(Instance::of(Value\UnsignedLongInteger::class)) - ->map(static fn($value) => $value->original()) - ->map(Count::of(...)) - ->map(PurgeOk::of(...)) - ->map(static fn() => State::of($connection, $state)) - ->either() - ->leftMap(fn() => Failure::toPurge($this->command)), - static fn($connection) => State::of($connection, $state), - ); + $frames = fn(Protocol $protocol): Sequence => $protocol->queue()->purge( + $channel, + $this->command, + ); + + $sideEffect = match ($this->command->shouldWait()) { + true => $connection + ->request($frames, Method::queuePurgeOk) + ->flatMap( + // this is here just to make sure the response is valid + // maybe in the future we could expose this info to the user + static fn($frame) => $frame + ->values() + ->first() + ->keep(Instance::of(Value\UnsignedLongInteger::class)) + ->map(static fn($value) => $value->original()) + ->map(Count::of(...)) + ->map(PurgeOk::of(...)) + ->either(), + ), + false => $connection->send($frames), + }; + + return $sideEffect + ->map(static fn() => $state) + ->leftMap(fn() => Failure::toPurge($this->command)); } public static function of(string $queue): self diff --git a/src/Command/Qos.php b/src/Command/Qos.php index b2881f1..1c20a35 100644 --- a/src/Command/Qos.php +++ b/src/Command/Qos.php @@ -28,17 +28,17 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ return $connection - ->send(fn($protocol) => $protocol->basic()->qos( - $channel, - $this->command, - )) - ->wait(Method::basicQosOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state)) + ->request( + fn($protocol) => $protocol->basic()->qos( + $channel, + $this->command, + ), + Method::basicQosOk, + ) + ->map(static fn() => $state) ->leftMap(static fn() => Failure::toAdjustQos()); } diff --git a/src/Command/Transaction.php b/src/Command/Transaction.php index eabba3b..c00de14 100644 --- a/src/Command/Transaction.php +++ b/src/Command/Transaction.php @@ -33,7 +33,7 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { return $this ->select($connection, $channel) @@ -43,7 +43,11 @@ public function __invoke( $read, $state, )) - ->flatMap(fn($state) => $this->finish($state, $channel)); + ->flatMap(fn($state) => $this->finish( + $connection, + $channel, + $state, + )); } /** @@ -71,52 +75,60 @@ private function select( Connection $connection, Channel $channel, ): Either { - /** @var Either */ return $connection - ->send(static fn($protocol) => $protocol->transaction()->select($channel)) - ->wait(Method::transactionSelectOk) - ->connection() + ->request( + static fn($protocol) => $protocol->transaction()->select($channel), + Method::transactionSelectOk, + ) + ->map(static fn() => $connection) ->leftMap(static fn() => Failure::toSelect()); } /** * @return Either */ - private function finish(State $state, Channel $channel): Either - { - return match (($this->predicate)($state->userState())) { - true => $this->commit($state, $channel), - false => $this->rollback($state, $channel), + private function finish( + Connection $connection, + Channel $channel, + State $state, + ): Either { + return match (($this->predicate)($state->unwrap())) { + true => $this->commit($connection, $channel, $state), + false => $this->rollback($connection, $channel, $state), }; } /** * @return Either */ - private function commit(State $state, Channel $channel): Either - { - /** @var Either */ - return $state - ->connection() - ->send(static fn($protocol) => $protocol->transaction()->commit($channel)) - ->wait(Method::transactionCommitOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state->userState())) + private function commit( + Connection $connection, + Channel $channel, + State $state, + ): Either { + return $connection + ->request( + static fn($protocol) => $protocol->transaction()->commit($channel), + Method::transactionCommitOk, + ) + ->map(static fn() => $state) ->leftMap(static fn() => Failure::toCommit()); } /** * @return Either */ - private function rollback(State $state, Channel $channel): Either - { - /** @var Either */ - return $state - ->connection() - ->send(static fn($protocol) => $protocol->transaction()->rollback($channel)) - ->wait(Method::transactionRollbackOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state->userState())) + private function rollback( + Connection $connection, + Channel $channel, + State $state, + ): Either { + return $connection + ->request( + static fn($protocol) => $protocol->transaction()->rollback($channel), + Method::transactionRollbackOk, + ) + ->map(static fn() => $state) ->leftMap(static fn() => Failure::toRollback()); } } diff --git a/src/Command/Unbind.php b/src/Command/Unbind.php index 553454b..e6c4b79 100644 --- a/src/Command/Unbind.php +++ b/src/Command/Unbind.php @@ -28,17 +28,17 @@ public function __invoke( Connection $connection, Channel $channel, MessageReader $read, - mixed $state, + State $state, ): Either { - /** @var Either */ return $connection - ->send(fn($protocol) => $protocol->queue()->unbind( - $channel, - $this->command, - )) - ->wait(Method::queueUnbindOk) - ->connection() - ->map(static fn($connection) => State::of($connection, $state)) + ->request( + fn($protocol) => $protocol->queue()->unbind( + $channel, + $this->command, + ), + Method::queueUnbindOk, + ) + ->map(static fn() => $state) ->leftMap(fn() => Failure::toUnbind($this->command)); } diff --git a/src/Consumer/Continuation.php b/src/Consumer/Continuation.php index 6ffd871..6709c77 100644 --- a/src/Consumer/Continuation.php +++ b/src/Consumer/Continuation.php @@ -8,7 +8,6 @@ Transport\Connection\MessageReader, Transport\Frame\Channel, Transport\Frame\Method, - Transport\Frame\Type, Model\Basic\Ack, Model\Basic\Reject, Model\Basic\Cancel, @@ -19,21 +18,21 @@ }; use Innmind\Immutable\{ Either, - Sequence, + SideEffect, }; final class Continuation { - private mixed $state; + private Client\State $state; private State $response; - private function __construct(mixed $state, State $response) + private function __construct(Client\State $state, State $response) { $this->state = $state; $this->response = $response; } - public static function of(mixed $state): self + public static function of(Client\State $state): self { // by default we auto ack the message return new self($state, State::ack); @@ -41,17 +40,17 @@ public static function of(mixed $state): self public function ack(mixed $state): self { - return new self($state, State::ack); + return new self(Client\State::of($state), State::ack); } public function reject(mixed $state): self { - return new self($state, State::reject); + return new self(Client\State::of($state), State::reject); } public function requeue(mixed $state): self { - return new self($state, State::requeue); + return new self(Client\State::of($state), State::requeue); } /** @@ -61,7 +60,7 @@ public function requeue(mixed $state): self */ public function cancel(mixed $state): self { - return new self($state, State::cancel); + return new self(Client\State::of($state), State::cancel); } /** @@ -83,22 +82,22 @@ public function respond( return match ($this->response) { State::cancel => $this ->doAck($queue, $connection, $channel, $deliveryTag) - ->flatMap(fn($connection) => $this->doCancel( + ->flatMap(fn() => $this->doCancel( $queue, $connection, $channel, $consumerTag, )) - ->flatMap(fn($connection) => $this->recover($queue, $connection, $channel, $read)), + ->flatMap(fn() => $this->recover($queue, $connection, $channel, $read)), State::ack => $this ->doAck($queue, $connection, $channel, $deliveryTag) - ->map(fn($connection) => Client\State::of($connection, $this->state)), + ->map(fn() => $this->state), State::reject => $this ->doReject($queue, $connection, $channel, $deliveryTag) - ->map(fn($connection) => Client\State::of($connection, $this->state)), + ->map(fn() => $this->state), State::requeue => $this ->doRequeue($queue, $connection, $channel, $deliveryTag) - ->map(fn($connection) => Client\State::of($connection, $this->state)), + ->map(fn() => $this->state), }; } @@ -113,7 +112,7 @@ private function recover( ): Either { $received = $connection->wait(); $walkOverPrefetchedMessages = $received->match( - static fn($received) => $received->frame()->is(Method::basicDeliver), + static fn($received) => $received->is(Method::basicDeliver), static fn() => false, ); @@ -121,12 +120,12 @@ private function recover( // read all the frames for the prefetched message then wait for next // frame $received = $received->flatMap( - static fn($received) => $read($received->connection())->flatMap( - static fn($received) => $received->connection()->wait(), + static fn() => $read($connection)->flatMap( + static fn() => $connection->wait(), ), ); $walkOverPrefetchedMessages = $received->match( - static fn($received) => $received->frame()->is(Method::basicDeliver), + static fn($received) => $received->is(Method::basicDeliver), static fn() => false, ); } @@ -140,24 +139,21 @@ private function recover( // messages handling /** @var Either */ return $received - ->flatMap( - static fn($received) => $received - ->connection() - ->send(static fn($protocol) => $protocol->basic()->recover( - $channel, - Recover::requeue(), - )) - ->wait(Method::basicRecoverOk) - ->connection(), - ) - ->map(fn($connection) => Canceled::of(Client\State::of($connection, $this->state))) + ->flatMap(static fn() => $connection->request( + static fn($protocol) => $protocol->basic()->recover( + $channel, + Recover::requeue(), + ), + Method::basicRecoverOk, + )) + ->map(fn() => Canceled::of($this->state)) ->leftMap(static fn() => Failure::toRecover($queue)); } /** * @param int<0, max> $deliveryTag * - * @return Either + * @return Either */ private function doAck( string $queue, @@ -165,20 +161,18 @@ private function doAck( Channel $channel, int $deliveryTag, ): Either { - /** @var Either */ return $connection ->send(static fn($protocol) => $protocol->basic()->ack( $channel, Ack::of($deliveryTag), )) - ->connection() ->leftMap(static fn() => Failure::toAck($queue)); } /** * @param int<0, max> $deliveryTag * - * @return Either + * @return Either */ private function doReject( string $queue, @@ -186,20 +180,18 @@ private function doReject( Channel $channel, int $deliveryTag, ): Either { - /** @var Either */ return $connection ->send(static fn($protocol) => $protocol->basic()->reject( $channel, Reject::of($deliveryTag), )) - ->connection() ->leftMap(static fn() => Failure::toReject($queue)); } /** * @param int<0, max> $deliveryTag * - * @return Either + * @return Either */ private function doRequeue( string $queue, @@ -207,18 +199,16 @@ private function doRequeue( Channel $channel, int $deliveryTag, ): Either { - /** @var Either */ return $connection ->send(static fn($protocol) => $protocol->basic()->reject( $channel, Reject::requeue($deliveryTag), )) - ->connection() ->leftMap(static fn() => Failure::toReject($queue)); } /** - * @return Either + * @return Either */ private function doCancel( string $queue, @@ -231,13 +221,11 @@ private function doCancel( throw new BasicGetNotCancellable; } - /** @var Either */ return $connection ->send(static fn($protocol) => $protocol->basic()->cancel( $channel, Cancel::of($consumerTag), )) - ->connection() ->leftMap(static fn() => Failure::toCancel($queue)); } } diff --git a/src/Exception/FrameChannelExceedAllowedChannelNumber.php b/src/Exception/FrameChannelExceedAllowedChannelNumber.php index 398e322..3317cb6 100644 --- a/src/Exception/FrameChannelExceedAllowedChannelNumber.php +++ b/src/Exception/FrameChannelExceedAllowedChannelNumber.php @@ -3,10 +3,7 @@ namespace Innmind\AMQP\Exception; -use Innmind\AMQP\{ - Model\Connection\MaxChannels, - Transport\Frame\Channel, -}; +use Innmind\AMQP\Model\Connection\MaxChannels; final class FrameChannelExceedAllowedChannelNumber extends LogicException { diff --git a/src/Factory.php b/src/Factory.php index 6ebaa9f..f0239c8 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -7,10 +7,6 @@ use Innmind\Socket\Internet\Transport as Socket; use Innmind\Url\Url; use Innmind\TimeContinuum\ElapsedPeriod; -use Innmind\Immutable\{ - Sequence, - Maybe, -}; final class Factory { @@ -37,13 +33,13 @@ public function make( $server, new Transport\Protocol( $this->os->clock(), - new Transport\Protocol\ArgumentTranslator\ValueTranslator, + new Transport\Protocol\ArgumentTranslator, ), $timeout, $this->os->clock(), $this->os->remote(), - $this->os->sockets(), ), + $this->os->filesystem(), ); } } diff --git a/src/Failure.php b/src/Failure.php index ac07f2c..1f0baf8 100644 --- a/src/Failure.php +++ b/src/Failure.php @@ -3,10 +3,7 @@ namespace Innmind\AMQP; -use Innmind\AMQP\{ - Model, - Transport\Frame\Method, -}; +use Innmind\AMQP\Transport\Frame\Method; use Innmind\Signals\Signal; use Innmind\Immutable\Maybe; diff --git a/src/Failure/ClosedBySignal.php b/src/Failure/ClosedBySignal.php index b6eb267..3513b02 100644 --- a/src/Failure/ClosedBySignal.php +++ b/src/Failure/ClosedBySignal.php @@ -3,10 +3,7 @@ namespace Innmind\AMQP\Failure; -use Innmind\AMQP\{ - Failure, - Transport\Frame\Method, -}; +use Innmind\AMQP\Failure; use Innmind\Signals\Signal; /** diff --git a/src/Transport/Connection.php b/src/Transport/Connection.php index 66f1a52..01f9ef6 100644 --- a/src/Transport/Connection.php +++ b/src/Transport/Connection.php @@ -9,44 +9,36 @@ Transport\Connection\OpenVHost, Transport\Connection\Heartbeat, Transport\Connection\FrameReader, - Transport\Connection\State, - Transport\Connection\Continuation, Transport\Connection\SignalListener, - Transport\Frame, - Transport\Protocol, Transport\Frame\Channel, Transport\Frame\Type, Transport\Frame\Method, Transport\Frame\Value, - Model\Connection\StartOk, - Model\Connection\SecureOk, - Model\Connection\TuneOk, - Model\Connection\Open, Model\Connection\Close, Model\Connection\MaxChannels, Model\Connection\MaxFrameSize, + Model\Connection\TuneOk, Failure, + Exception\FrameChannelExceedAllowedChannelNumber, + Exception\FrameExceedAllowedSize, }; use Innmind\OperatingSystem\CurrentProcess\Signals; use Innmind\Socket\{ Internet\Transport, Client as Socket, }; -use Innmind\Stream\Watch; +use Innmind\IO\{ + Sockets\Client, + Readable\Frame as IOFrame, +}; use Innmind\Url\Url; use Innmind\TimeContinuum\{ ElapsedPeriod, Clock, - PointInTime, - Earth, -}; -use Innmind\OperatingSystem\{ - Remote, - Sockets, }; +use Innmind\OperatingSystem\Remote; use Innmind\Immutable\{ Str, - Set, Maybe, Either, Sequence, @@ -60,31 +52,31 @@ final class Connection { private Protocol $protocol; - private Sockets $sockets; - private Socket $socket; - private Watch $watch; - private FrameReader $read; + /** @var Client */ + private Client $socket; + /** @var IOFrame */ + private IOFrame $frame; private MaxChannels $maxChannels; private MaxFrameSize $maxFrameSize; private Heartbeat $heartbeat; private SignalListener $signals; + /** + * @param Client $socket + * @param IOFrame $frame + */ private function __construct( Protocol $protocol, - Sockets $sockets, Heartbeat $heartbeat, - Socket $socket, - Watch $watch, + Client $socket, MaxChannels $maxChannels, MaxFrameSize $maxFrameSize, - FrameReader $read, + IOFrame $frame, SignalListener $signals, ) { $this->protocol = $protocol; - $this->sockets = $sockets; $this->socket = $socket; - $this->watch = $watch; - $this->read = $read; + $this->frame = $frame; $this->maxChannels = $maxChannels; $this->maxFrameSize = $maxFrameSize; $this->heartbeat = $heartbeat; @@ -101,32 +93,30 @@ public static function open( ElapsedPeriod $timeout, Clock $clock, Remote $remote, - Sockets $sockets, ): Maybe { - /** - * Due to the $socket->write() psalm lose the type - * @psalm-suppress ArgumentTypeCoercion - * @psalm-suppress InvalidArgument - */ + /** @psalm-suppress InvalidArgument */ return $remote ->socket( $transport, $server->authority()->withoutUserInformation(), ) + ->map( + static fn($socket) => $socket + ->timeoutAfter($timeout) + ->toEncoding(Str\Encoding::ascii), + ) ->flatMap( static fn($socket) => $socket - ->write($protocol->version()->pack()) - ->maybe(), + ->send(Sequence::of($protocol->version()->pack())) + ->map(static fn() => $socket), ) ->map(static fn($socket) => new self( $protocol, - $sockets, Heartbeat::start($clock, $timeout), $socket, - $sockets->watch($timeout)->forRead($socket), MaxChannels::unlimited(), MaxFrameSize::unlimited(), - new FrameReader, + (new FrameReader)($protocol), SignalListener::uninstalled(), )) ->flatMap(new Start($server->authority())) @@ -135,64 +125,71 @@ public static function open( } /** - * When it contains the same connection instance it means that you can still - * use the connection, otherwise you should stop using it - * - * Possible failures are exceeding the max channel number, the max frame size - * or that writting to the socket failed + * @param callable(Protocol, MaxFrameSize): Sequence $frames * + * @return Either + */ + public function respondTo(Method $method, callable $frames): Either + { + return $this + ->wait($method) + ->flatMap( + fn() => $this->sendFrames($frames), + ); + } + + /** * @param callable(Protocol, MaxFrameSize): Sequence $frames + * + * @return Either */ - public function send(callable $frames): Continuation + public function request(callable $frames, Method $method, Method ...$methods): Either { - /** - * @psalm-suppress MixedArgumentTypeCoercion - * @var Either - */ - $connection = $frames($this->protocol, $this->maxFrameSize)->reduce( - $this->signals->safe($this), - static fn(Either $connection, $frame) => $connection->flatMap( - static fn(self $connection) => $connection->sendFrame($frame), - ), - ); + return $this + ->sendFrames($frames) + ->flatMap(fn() => $this->wait($method, ...$methods)) + ->map(static fn($received) => $received->frame()); + } - return Continuation::of($connection); + /** + * @param callable(Protocol, MaxFrameSize): Sequence $frames + * + * @return Either + */ + public function send(callable $frames): Either + { + return $this->sendFrames($frames); } /** * @return Either */ - public function wait(Frame\Method ...$names): Either + public function wait(Method ...$names): Either { - /** @var Either}> */ - $ready = Either::right([$this, Set::of()]); - - do { - $ready = $ready->flatMap($this->doWait(...)); - } while ($ready->match( - static fn($ready) => !$ready[1]->contains($ready[0]->socket), - static fn() => false, - )); - - return $ready - ->maybe() - ->map(static fn($ready) => $ready[0]) - ->flatMap( - static fn($connection) => ($connection->read)( - $connection->socket, - $connection->protocol, - ) - ->map(static fn($frame) => ReceivedFrame::of( - $connection->asActive(), - $frame, - )), + return $this + ->socket + ->heartbeatWith( + fn() => $this + ->heartbeat + ->frames() + ->map(static fn($frame) => $frame->pack()), ) + ->abortWhen($this->signals->notified(...)) + ->frames($this->frame) + ->one() + ->map(ReceivedFrame::of(...)) + ->map($this->flagActive(...)) ->either() - ->leftMap(static fn() => Failure::toReadFrame()) - ->flatMap(static fn($received) => match ($received->frame()->type()) { - Type::heartbeat => $received->connection()->wait(...$names), - default => $received->connection()->ensureValidFrame($received, ...$names), - }); + ->eitherWay( + fn($received) => match ($received->frame()->type()) { + Type::heartbeat => $this->wait(...$names), + default => $this->ensureValidFrame($received, ...$names), + }, + fn() => $this->signals->close( + $this, + static fn() => Either::left(Failure::toReadFrame()), + ), + ); } /** @@ -200,125 +197,104 @@ public function wait(Frame\Method ...$names): Either */ public function close(): Maybe { + $this->signals->uninstall(); + if ($this->closed()) { /** @var Maybe */ return Maybe::nothing(); } return $this - ->send(static fn($protocol) => $protocol->connection()->close(Close::demand())) - ->wait(Method::connectionCloseOk) - ->connection() - ->flatMap(static fn($connection) => $connection->socket->close()) + ->request( + static fn($protocol) => $protocol->connection()->close(Close::demand()), + Method::connectionCloseOk, + ) + ->flatMap(fn() => $this->socket->unwrap()->close()) ->maybe() ->map(static fn() => new SideEffect); } /** - * This only modify the internal values for the connection, it doesn't - * notify the server we applied the changes on our end. The notification is - * done in Handshake - * - * @internal + * @return Maybe */ public function tune( MaxChannels $maxChannels, MaxFrameSize $maxFrameSize, ElapsedPeriod $heartbeat, - ): self { - return new self( - $this->protocol, - $this->sockets, - $this->heartbeat->adjust($heartbeat), - $this->socket, - $this->sockets->watch($heartbeat)->forRead($this->socket), - $maxChannels, - $maxFrameSize, - $this->read, - $this->signals, - ); + ): Maybe { + return $this + ->send(static fn($protocol) => $protocol->connection()->tuneOk( + TuneOk::of( + $maxChannels, + $maxFrameSize, + $heartbeat, + ), + )) + ->maybe() + ->map(fn() => new self( + $this->protocol, + $this->heartbeat->adjust($heartbeat), + $this->socket->timeoutAfter($heartbeat), + $maxChannels, + $maxFrameSize, + $this->frame, + $this->signals, + )); } - /** - * @internal - */ - public function asActive(): self + public function listenSignals(Signals $signals, Channel $channel): void { - return new self( - $this->protocol, - $this->sockets, - $this->heartbeat->active(), - $this->socket, - $this->watch, - $this->maxChannels, - $this->maxFrameSize, - $this->read, - $this->signals, - ); + $this->signals->install($signals, $channel); } - public function listenSignals(Signals $signals, Channel $channel): self + private function flagActive(ReceivedFrame $received): ReceivedFrame { - return new self( - $this->protocol, - $this->sockets, - $this->heartbeat, - $this->socket, - $this->watch, - $this->maxChannels, - $this->maxFrameSize, - $this->read, - $this->signals->install($signals, $channel), - ); - } + $this->heartbeat->active(); - /** - * @return Either - */ - private function sendFrame(Frame $frame): Either - { - /** @var Either */ - return Maybe::just($frame) - ->filter(fn($frame) => $this->maxChannels->allows($frame->channel()->toInt())) - ->map(static fn($frame) => $frame->pack()->toEncoding(Str\Encoding::ascii)) - ->filter(fn($frame) => $this->maxFrameSize->allows($frame->length())) - ->flatMap( - fn($frame) => $this - ->socket - ->write($frame) - ->maybe(), - ) - ->map(fn() => $this) - ->either() - ->leftMap(static fn() => Failure::toSendFrame()); + return $received; } /** - * @param array{Connection, Set} $in + * When it contains the same connection instance it means that you can still + * use the connection, otherwise you should stop using it + * + * Possible failures are exceeding the max channel number, the max frame size + * or that writting to the socket failed * - * @return Either}> + * @param callable(Protocol, MaxFrameSize): Sequence $frames + * + * @throws FrameChannelExceedAllowedChannelNumber + * @throws FrameExceedAllowedSize + * + * @return Either */ - private function doWait(array $in): Either + private function sendFrames(callable $frames): Either { - [$connection] = $in; + $data = $frames($this->protocol, $this->maxFrameSize) + ->map(function($frame) { + $this->maxChannels->verify($frame->channel()->toInt()); - if ($connection->closed()) { - /** @var Either}> */ - return Either::left(Failure::toReadFrame()); - } + return $frame; + }) + ->map(static fn($frame) => $frame->pack()) + ->map(function($frame) { + $this->maxFrameSize->verify($frame->length()); - /** @var Either}> */ - return $connection - ->signals - ->safe($connection) - ->flatMap(static fn($connection) => $connection->heartbeat->ping($connection)) - ->map(static fn($connection) => [ - $connection, - ($connection->watch)()->match( - static fn($ready) => $ready->toRead(), - static fn() => Set::of(), + return $frame; + }); + + return $this + ->socket + ->abortWhen($this->signals->notified(...)) + ->send($data) + ->either() + ->eitherWay( + static fn() => Either::right(new SideEffect), + fn() => $this->signals->close( + $this, + static fn() => Either::left(Failure::toSendFrame()), ), - ]); + ); } /** @@ -344,12 +320,10 @@ private function ensureValidFrame( return Either::right($received); } - if ($received->frame()->is(Method::connectionClose)) { + if ($received->is(Method::connectionClose)) { /** @var Either */ - return $received - ->connection() + return $this ->send(static fn($protocol) => $protocol->connection()->closeOk()) - ->connection() ->leftMap(static fn() => Failure::toCloseConnection()) ->flatMap(static function() use ($received) { $message = $received @@ -400,6 +374,6 @@ private function ensureValidFrame( private function closed(): bool { - return $this->socket->closed(); + return $this->socket->unwrap()->closed(); } } diff --git a/src/Transport/Connection/Continuation.php b/src/Transport/Connection/Continuation.php deleted file mode 100644 index f455ed7..0000000 --- a/src/Transport/Connection/Continuation.php +++ /dev/null @@ -1,104 +0,0 @@ - */ - private Either $connection; - - /** - * @param Either $connection - */ - private function __construct(Either $connection) - { - $this->connection = $connection; - } - - /** - * @param Either $connection - */ - public static function of(Either $connection): self - { - /** @psalm-suppress InvalidArgument Because it's always Connection */ - return new self($connection); - } - - /** - * @no-named-arguments - */ - public function wait(Method ...$methods): self - { - /** @psalm-suppress InvalidArgument Because the right side is always ReceivedFrame */ - return new self($this->connection->flatMap( - static fn($connection) => match (true) { - $connection instanceof Connection => $connection->wait(...$methods), - default => throw new LogicException("Can't call wait multiple times"), - }, - )); - } - - public function maybeWait(bool $wait, Method $method): self - { - if (!$wait) { - return $this; - } - - /** @psalm-suppress InvalidArgument Because the right side is always ReceivedFrame */ - return new self($this->connection->flatMap( - static fn($connection) => match (true) { - $connection instanceof Connection => $connection->wait($method), - default => throw new LogicException("Can't call wait multiple times"), - }, - )); - } - - /** - * @template R - * - * @param callable(Connection, Frame): Either $withFrame - * @param callable(Connection): R $withoutFrame - * - * @return Either - */ - public function then(callable $withFrame, callable $withoutFrame): Either - { - /** @psalm-suppress InvalidArgument Due to Either::right call */ - return $this - ->connection - ->flatMap(static fn($received) => match (true) { - $received instanceof Connection => Either::right($withoutFrame($received)), - default => $withFrame($received->connection(), $received->frame()), - }); - } - - /** - * @return Either - */ - public function connection(): Either - { - return $this->connection->map( - static fn($received) => match (true) { - $received instanceof Connection => $received, - default => $received->connection(), - }, - ); - } -} diff --git a/src/Transport/Connection/FrameReader.php b/src/Transport/Connection/FrameReader.php index 2257c69..1084b2b 100644 --- a/src/Transport/Connection/FrameReader.php +++ b/src/Transport/Connection/FrameReader.php @@ -14,97 +14,92 @@ Frame\Value\UnsignedShortInteger, Frame\Value\UnsignedLongInteger, }; -use Innmind\Stream\Readable; -use Innmind\Immutable\Maybe; +use Innmind\IO\Readable\Frame as IOFrame; +use Innmind\Immutable\Str; /** * @internal + * @psalm-immutable */ final class FrameReader { /** - * @return Maybe + * @return IOFrame */ - public function __invoke(Readable $stream, Protocol $protocol): Maybe + public function __invoke(Protocol $protocol): IOFrame { - return $this - ->readType($stream) - ->flatMap( - fn($type) => $this - ->readChannel($stream) - ->flatMap(fn($channel) => $this->readFrame( - $type, - $channel, - $stream, - $protocol, - )), - ); + return $this->readType()->flatMap( + fn($type) => $this + ->readChannel() + ->flatMap(fn($channel) => $this->readFrame( + $type, + $channel, + $protocol, + )), + ); } /** - * @return Maybe + * @return IOFrame */ private function readFrame( Type $type, Channel $channel, - Readable $stream, Protocol $protocol, - ): Maybe { - /** @psalm-suppress InvalidArgument */ - return UnsignedLongInteger::unpack($stream) - ->map(static fn($value) => $value->original()) + ): IOFrame { + return UnsignedLongInteger::frame() + ->map(static fn($value) => $value->unwrap()->original()) ->flatMap(fn($length) => match ($type) { - Type::method => $this->readMethod($stream, $protocol, $channel), - Type::header => $this->readHeader($stream, $protocol, $channel), - Type::body => $this->readBody($stream, $channel, $length), - Type::heartbeat => Maybe::just(Frame::heartbeat()), + Type::method => $this->readMethod($protocol, $channel), + Type::header => $this->readHeader($protocol, $channel), + Type::body => $this->readBody($channel, $length), + Type::heartbeat => IOFrame\NoOp::of(Frame::heartbeat()), }) ->flatMap( - static fn($frame) => UnsignedOctet::unpack($stream) - ->map(static fn($end) => $end->original()) + static fn($frame) => UnsignedOctet::frame() + ->map(static fn($end) => $end->unwrap()->original()) ->filter(static fn($end) => $end === Frame::end()) ->map(static fn() => $frame), ); } /** - * @return Maybe + * @return IOFrame */ - private function readType(Readable $stream): Maybe + private function readType(): IOFrame { - return UnsignedOctet::unpack($stream) - ->map(static fn($octet) => $octet->original()) - ->flatMap(Type::maybe(...)); + return UnsignedOctet::frame() + ->map(static fn($octet) => $octet->unwrap()->original()) + ->flatMap(Type::frame(...)); } /** - * @return Maybe + * @return IOFrame */ - private function readChannel(Readable $stream): Maybe + private function readChannel(): IOFrame { - return UnsignedShortInteger::unpack($stream) - ->map(static fn($value) => $value->original()) + return UnsignedShortInteger::frame() + ->map(static fn($value) => $value->unwrap()->original()) ->map(static fn($value) => new Channel($value)); } /** - * @return Maybe + * @return IOFrame */ private function readMethod( - Readable $payload, Protocol $protocol, Channel $channel, - ): Maybe { - return UnsignedShortInteger::unpack($payload) - ->map(static fn($value) => $value->original()) + ): IOFrame { + return UnsignedShortInteger::frame() + ->map(static fn($value) => $value->unwrap()->original()) ->flatMap( - static fn($class) => UnsignedShortInteger::unpack($payload) - ->map(static fn($value) => $value->original()) - ->flatMap(static fn($method) => Method::maybe($class, $method)), + static fn($class) => UnsignedShortInteger::frame() + ->map(static fn($value) => $value->unwrap()->original()) + ->flatMap(static fn($method) => Method::frame($class, $method)), ) ->flatMap( static fn($method) => $protocol - ->read($method, $payload) + ->frame($method) ->map(static fn($values) => Frame::method( $channel, $method, @@ -114,24 +109,22 @@ private function readMethod( } /** - * @return Maybe + * @return IOFrame */ private function readHeader( - Readable $payload, Protocol $protocol, Channel $channel, - ): Maybe { - return UnsignedShortInteger::unpack($payload) - ->map(static fn($value) => $value->original()) - ->flatMap(MethodClass::maybe(...)) + ): IOFrame { + return UnsignedShortInteger::frame() + ->map(static fn($value) => $value->unwrap()->original()) + ->flatMap(MethodClass::frame(...)) ->flatMap( - static fn($class) => $payload - ->read(2) // walk over the weight definition + static fn($class) => IOFrame\Chunk::of(2) // walk over the weight definition ->map(static fn() => $class), ) ->flatMap( static fn($class) => $protocol - ->readHeader($payload) + ->headerFrame() ->map(static fn($arguments) => Frame::header( $channel, $class, @@ -143,16 +136,16 @@ private function readHeader( /** * @param int<0, 4294967295> $length * - * @return Maybe + * @return IOFrame */ private function readBody( - Readable $payload, Channel $channel, int $length, - ): Maybe { - /** @psalm-suppress InvalidArgument */ - return $payload - ->read($length) + ): IOFrame { + return (match ($length) { + 0 => IOFrame\NoOp::of(Str::of('')), + default => IOFrame\Chunk::of($length), + }) ->map(static fn($data) => Frame::body($channel, $data)); } } diff --git a/src/Transport/Connection/Handshake.php b/src/Transport/Connection/Handshake.php index 81bc16d..6beca6a 100644 --- a/src/Transport/Connection/Handshake.php +++ b/src/Transport/Connection/Handshake.php @@ -9,7 +9,6 @@ Transport\Frame\Method, Transport\Frame\Value, Model\Connection\SecureOk, - Model\Connection\TuneOk, Model\Connection\MaxChannels, Model\Connection\MaxFrameSize, Failure, @@ -41,9 +40,9 @@ public function __invoke(Connection $connection): Maybe { return $connection ->wait(Method::connectionSecure, Method::connectionTune) - ->flatMap(fn($received) => match ($received->frame()->is(Method::connectionSecure)) { - true => $this->secure($received->connection()), - false => $this->maybeTune($received->connection(), $received->frame()), + ->flatMap(fn($received) => match ($received->is(Method::connectionSecure)) { + true => $this->secure($connection), + false => $this->maybeTune($connection, $received->frame()), }) ->maybe(); } @@ -54,17 +53,16 @@ public function __invoke(Connection $connection): Maybe private function secure(Connection $connection): Either { return $connection - ->send(fn($protocol) => $protocol->connection()->secureOk( - SecureOk::of( - $this->authority->userInformation()->user(), - $this->authority->userInformation()->password(), + ->request( + fn($protocol) => $protocol->connection()->secureOk( + SecureOk::of( + $this->authority->userInformation()->user(), + $this->authority->userInformation()->password(), + ), ), - )) - ->wait(Method::connectionTune) - ->then( - $this->maybeTune(...), - static fn($connection) => $connection, - ); + Method::connectionTune, + ) + ->flatMap(fn($frame) => $this->maybeTune($connection, $frame)); } /** @@ -92,35 +90,8 @@ private function maybeTune(Connection $connection, Frame $frame): Either ->map(ElapsedPeriod::of(...)); return Maybe::all($maxChannels, $maxFrameSize, $heartbeat) - ->flatMap(fn(MaxChannels $maxChannels, MaxFrameSize $maxFrameSize, ElapsedPeriod $heartbeat) => $this->tune( - $connection, - $maxChannels, - $maxFrameSize, - $heartbeat, - )) + ->flatMap($connection->tune(...)) ->either() ->leftMap(static fn() => Failure::toOpenConnection()); } - - /** - * @return Maybe - */ - private function tune( - Connection $connection, - MaxChannels $maxChannels, - MaxFrameSize $maxFrameSize, - ElapsedPeriod $heartbeat, - ): Maybe { - return $connection - ->tune($maxChannels, $maxFrameSize, $heartbeat) - ->send(static fn($protocol) => $protocol->connection()->tuneOk( - TuneOk::of( - $maxChannels, - $maxFrameSize, - $heartbeat, - ), - )) - ->connection() - ->maybe(); - } } diff --git a/src/Transport/Connection/Heartbeat.php b/src/Transport/Connection/Heartbeat.php index 1dc9ed2..cdac261 100644 --- a/src/Transport/Connection/Heartbeat.php +++ b/src/Transport/Connection/Heartbeat.php @@ -4,19 +4,14 @@ namespace Innmind\AMQP\Transport\Connection; use Innmind\AMQP\{ - Transport\Connection, Transport\Frame, - Failure, }; use Innmind\TimeContinuum\{ Clock, PointInTime, ElapsedPeriod, }; -use Innmind\Immutable\{ - Sequence, - Either, -}; +use Innmind\Immutable\Sequence; /** * @internal @@ -43,9 +38,9 @@ public static function start(Clock $clock, ElapsedPeriod $threshold): self } /** - * @return Either + * @return Sequence */ - public function ping(Connection $connection): Either + public function frames(): Sequence { if ( $this @@ -54,29 +49,25 @@ public function ping(Connection $connection): Either ->elapsedSince($this->lastReceivedData) ->longerThan($this->threshold) ) { - return $connection - ->send(static fn() => Sequence::of(Frame::heartbeat())) - ->connection(); + $this->lastReceivedData = $this->clock->now(); + + return Sequence::of(Frame::heartbeat()); } - /** @var Either */ - return Either::right($connection); + return Sequence::of(); } - public function active(): self + public function active(): void { - return new self( - $this->clock, - $this->threshold, - $this->clock->now(), - ); + $this->lastReceivedData = $this->clock->now(); } public function adjust(ElapsedPeriod $threshold): self { - $self = clone $this; - $self->threshold = $threshold; - - return $self; + return new self( + $this->clock, + $threshold, + $this->lastReceivedData, + ); } } diff --git a/src/Transport/Connection/MessageReader.php b/src/Transport/Connection/MessageReader.php index 2e40d4b..9278493 100644 --- a/src/Transport/Connection/MessageReader.php +++ b/src/Transport/Connection/MessageReader.php @@ -16,23 +16,15 @@ Model\Basic\Message\UserId, Model\Basic\Message\AppId, Transport\Connection, - Transport\ReceivedMessage, Transport\Frame, Transport\Frame\Value, Failure, }; -use Innmind\TimeContinuum\{ - Earth, - ElapsedPeriod, -}; +use Innmind\OperatingSystem\Filesystem; +use Innmind\TimeContinuum\Earth\ElapsedPeriod; use Innmind\Filesystem\File\Content; -use Innmind\IO\IO; -use Innmind\Stream\{ - Capabilities, - Bidirectional, -}; +use Innmind\Stream\Stream\Size\Unit; use Innmind\Immutable\{ - Map, Str, Predicate\Instance, Sequence, @@ -45,39 +37,38 @@ */ final class MessageReader { - private Capabilities $streams; + private Filesystem $filesystem; - private function __construct(Capabilities $streams) + private function __construct(Filesystem $filesystem) { - $this->streams = $streams; + $this->filesystem = $filesystem; } /** - * @return Either + * @return Either */ public function __invoke(Connection $connection): Either { return $connection ->wait() ->flatMap(fn($received) => $this->decode( - $received->connection(), + $connection, $received->frame(), )); } - public static function of(Capabilities $streams): self + public static function of(Filesystem $filesystem): self { - return new self($streams); + return new self($filesystem); } /** - * @return Either + * @return Either */ private function decode( Connection $connection, Frame $header, ): Either { - /** @var Either */ return $header ->values() ->first() @@ -87,7 +78,7 @@ private function decode( ->leftMap(static fn() => Failure::toReadMessage()) ->flatMap(fn($bodySize) => $this->readMessage($connection, $bodySize)) ->flatMap( - fn($received) => $header + fn($message) => $header ->values() ->get(1) ->keep(Instance::of(Value\UnsignedShortInteger::class)) @@ -95,15 +86,11 @@ private function decode( ->either() ->leftMap(static fn() => Failure::toReadMessage()) ->flatMap(fn($flagBits) => $this->addProperties( - $received->message(), + $message, $flagBits, $header ->values() ->drop(2), // for bodySize and flagBits - )) - ->map(static fn($message) => ReceivedMessage::of( - $received->connection(), - $message, )), ); } @@ -186,7 +173,7 @@ private function addProperties( static fn(Maybe $value, Message $message) => $value ->keep(Instance::of(Value\ShortString::class)) ->map(static fn($value) => (int) $value->original()->toString()) - ->flatMap(Earth\ElapsedPeriod::maybe(...)) + ->flatMap(ElapsedPeriod::maybe(...)) ->map(static fn($expiration) => $message->withExpiration($expiration)), ], [ @@ -252,78 +239,54 @@ private function addProperties( } /** - * @return Either + * @return Either */ private function readMessage( Connection $connection, int $bodySize, ): Either { - $walk = $bodySize !== 0; - $stream = $this->streams->temporary()->new(); - $read = Either::right([$connection, $stream, 0]); + $chunks = Sequence::lazy(static function() use ($connection, $bodySize) { + $continue = $bodySize !== 0; + $read = 0; - while ($walk) { - $read = $read->flatMap($this->readChunk(...)); - $walk = $read->match( - static fn($read) => $read[2] !== $bodySize, - static fn() => false, // because no content was found in the last frame or failed to write the chunk to the temp stream - ); - } + while ($continue) { + $chunk = $connection + ->wait() + ->maybe() + ->flatMap(static fn($received) => $received->frame()->content()) + ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)); + $read += $chunk->match( + static fn($chunk) => $chunk->length(), + static fn() => 0, + ); + $continue = $chunk->match( + static fn() => $read !== $bodySize, + static fn() => false, + ); - $io = IO::of(fn(?ElapsedPeriod $timeout) => match ($timeout) { - null => $this->streams->watch()->waitForever(), - default => $this->streams->watch()->timeoutAfter($timeout), + yield $chunk; + } }); - return $read->map(static fn($in) => ReceivedMessage::of( - $in[0], - Message::file(Content::io($io->readable()->wrap($in[1]))), - )); - } + /** @psalm-suppress MixedArgumentTypeCoercion Because of the reduce it doesn't understand the type of the Sequence */ + $content = match (true) { + $bodySize <= Unit::megabytes->times(2) => $chunks + ->reduce( + Maybe::just(Sequence::of()), + static fn(Maybe $content, $chunk) => Maybe::all($content, $chunk)->map( + static fn(Sequence $chunks, Str $chunk) => ($chunks)($chunk), + ), + ) + ->map(Content::ofChunks(...)), + default => $this + ->filesystem + ->temporary($chunks) + ->memoize(), // to prevent using a deferred Maybe that would result in out of order reading the socket + }; - /** - * @param array{Connection, Bidirectional, int} $in - * - * @return Either - */ - private function readChunk(array $in): Either - { - [$connection, $stream, $read] = $in; - - return $connection - ->wait() - ->flatMap( - fn($received) => $this - ->accumulateChunk($received->frame(), $stream, $read) - ->map(static function($in) use ($received) { - [$stream, $read] = $in; - - return [$received->connection(), $stream, $read]; - }), - ); - } - - /** - * @return Either - */ - private function accumulateChunk( - Frame $frame, - Bidirectional $stream, - int $read, - ): Either { - /** @var Either */ - return $frame - ->content() + return $content ->either() - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->flatMap( - static fn($chunk) => $stream - ->write($chunk) - ->map(static fn($stream) => [ - $stream, - $read + $chunk->length(), - ]), - ) + ->map(Message::file(...)) ->leftMap(static fn() => Failure::toReadMessage()); } } diff --git a/src/Transport/Connection/OpenVHost.php b/src/Transport/Connection/OpenVHost.php index 742fcaf..6f5c194 100644 --- a/src/Transport/Connection/OpenVHost.php +++ b/src/Transport/Connection/OpenVHost.php @@ -29,11 +29,13 @@ public function __construct(Path $vhost) public function __invoke(Connection $connection): Maybe { return $connection - ->send(fn($protocol) => $protocol->connection()->open( - Open::of($this->vhost), - )) - ->wait(Method::connectionOpenOk) - ->connection() - ->maybe(); + ->request( + fn($protocol) => $protocol->connection()->open( + Open::of($this->vhost), + ), + Method::connectionOpenOk, + ) + ->maybe() + ->map(static fn() => $connection); } } diff --git a/src/Transport/Connection/SignalListener.php b/src/Transport/Connection/SignalListener.php index 269f2a9..861bef4 100644 --- a/src/Transport/Connection/SignalListener.php +++ b/src/Transport/Connection/SignalListener.php @@ -13,35 +13,46 @@ use Innmind\OperatingSystem\CurrentProcess\Signals; use Innmind\Signals\Signal; use Innmind\Immutable\{ - Set, Either, Maybe, }; /** - * This class cannot have an immutable like behaviour as the signal is sents + * This class cannot have an immutable like behaviour as the signal is sent * asynchronously so we need to change a state */ final class SignalListener { private bool $installed = false; + private bool $notified = false; /** @var Maybe */ - private Maybe $notified; - /** - * Even though our Client only support one channel per connection we use a - * Set here in case womeone figures out how to work with multiple channels - * on a single connection - * @var Set - */ - private Set $channels; + private Maybe $received; + /** @var Maybe */ + private Maybe $channel; + /** @var Maybe */ + private Maybe $signals; + /** @var \Closure(Signal): void */ + private \Closure $softClose; private bool $closing = false; private function __construct() { /** @var Maybe */ - $this->notified = Maybe::nothing(); - /** @var Set */ - $this->channels = Set::of(); + $this->received = Maybe::nothing(); + /** @var Maybe */ + $this->channel = Maybe::nothing(); + /** @var Maybe */ + $this->signals = Maybe::nothing(); + $this->softClose = function(Signal $signal): void { + // Do not re-attempt to close when already closing if the user sends + // multiple signals. + if ($this->closing) { + return; + } + + $this->notified = true; + $this->received = Maybe::just($signal); + }; } public static function uninstalled(): self @@ -49,87 +60,79 @@ public static function uninstalled(): self return new self; } - public function install(Signals $signals, Channel $channel): self + public function install(Signals $signals, Channel $channel): void { if (!$this->installed) { - $softClose = function(Signal $signal): void { - $this->notified = Maybe::just($signal); - }; $signals->listen(Signal::hangup, static function() { // do nothing so it can run in background }); - $signals->listen(Signal::interrupt, $softClose); - $signals->listen(Signal::abort, $softClose); - $signals->listen(Signal::terminate, $softClose); - $signals->listen(Signal::terminalStop, $softClose); - $signals->listen(Signal::alarm, $softClose); + $signals->listen(Signal::interrupt, $this->softClose); + $signals->listen(Signal::abort, $this->softClose); + $signals->listen(Signal::terminate, $this->softClose); + $signals->listen(Signal::terminalStop, $this->softClose); + $signals->listen(Signal::alarm, $this->softClose); + $this->signals = Maybe::just($signals); $this->installed = true; } - $this->channels = ($this->channels)($channel); - - return $this; + $this->channel = Maybe::just($channel); } - /** - * @return Either - */ - public function safe(Connection $connection): Either + public function uninstall(): void { - return $this->notified->match( - fn($signal) => $this->close($connection, $signal), - static fn() => Either::right($connection), + $_ = $this->signals->match( + fn($signals) => $signals->remove($this->softClose), + static fn() => null, ); } - /** - * @return Either - */ - private function close(Connection $connection, Signal $signal): Either + public function notified(): bool { + // Return false when closing to avoid abort watching the socket during + // the handshake to properly close the connection. if ($this->closing) { - return Either::right($connection); + return false; } - $this->closing = true; - - $closed = $this - ->channels - ->reduce( - Either::right($connection), - $this->closeChannel(...), - ) - ->flatMap( - static fn($connection) => $connection - ->close() - ->either() - ->leftMap(static fn() => Failure::toCloseConnection()) - ->flatMap(static fn() => Either::left(Failure::closedBySignal($signal))), - ); - $this->closing = false; // technically this method should never be called twice - - return $closed; + return $this->notified; } /** - * @param Either $connection + * @template T + * + * @param callable(): Either $continue * - * @return Either + * @return Either */ - private function closeChannel(Either $connection, Channel $channel): Either + public function close(Connection $connection, callable $continue): Either { - return $connection - ->map(static fn($connection) => $connection->send( - static fn($protocol) => $protocol->channel()->close( - $channel, - Close::demand(), - ), - )) - ->map(static fn($continuation) => $continuation->wait(Method::channelCloseOk)) - ->flatMap( - static fn($continuation) => $continuation - ->connection() - ->leftMap(static fn() => Failure::toCloseChannel()), + return Maybe::all($this->received, $this->channel) + ->map(static fn(Signal $signal, Channel $channel) => [$signal, $channel]) + ->filter(fn() => !$this->closing) + ->either() + ->match( + function($in) use ($connection) { + $this->closing = true; + [$signal, $channel] = $in; + + return $connection + ->request( + static fn($protocol) => $protocol->channel()->close( + $channel, + Close::demand(), + ), + Method::channelCloseOk, + ) + ->leftMap(static fn() => Failure::toCloseChannel()) + ->flatMap( + static fn() => $connection + ->close() + ->either() + ->leftMap(static fn() => Failure::toCloseConnection()), + ) + ->flatMap(static fn() => Either::left(Failure::closedBySignal($signal))); + }, + static fn() => $continue(), ); } } diff --git a/src/Transport/Connection/Start.php b/src/Transport/Connection/Start.php index e46e4b4..67a5ae0 100644 --- a/src/Transport/Connection/Start.php +++ b/src/Transport/Connection/Start.php @@ -33,16 +33,17 @@ public function __invoke(Connection $connection): Maybe // we should restart the opening sequence with this version of the // protocol but since this package only support 0.9.1 we can simply // stop opening the connection - $connection->wait(Method::connectionStart); - return $connection - ->send(fn($protocol) => $protocol->connection()->startOk( - StartOk::of( - $this->authority->userInformation()->user(), - $this->authority->userInformation()->password(), + ->respondTo( + Method::connectionStart, + fn($protocol) => $protocol->connection()->startOk( + StartOk::of( + $this->authority->userInformation()->user(), + $this->authority->userInformation()->password(), + ), ), - )) - ->connection() - ->maybe(); + ) + ->maybe() + ->map(static fn() => $connection); } } diff --git a/src/Transport/Frame.php b/src/Transport/Frame.php index 3b342f3..251bd85 100644 --- a/src/Transport/Frame.php +++ b/src/Transport/Frame.php @@ -12,7 +12,6 @@ Value\UnsignedOctet, Value\UnsignedShortInteger, Value\UnsignedLongInteger, - Value\Text, }; use Innmind\Immutable\{ Sequence, diff --git a/src/Transport/Frame/Method.php b/src/Transport/Frame/Method.php index 85cd7ce..9b961ca 100644 --- a/src/Transport/Frame/Method.php +++ b/src/Transport/Frame/Method.php @@ -3,7 +3,24 @@ namespace Innmind\AMQP\Transport\Frame; -use Innmind\Immutable\Maybe; +use Innmind\AMQP\Transport\Frame\Value\{ + Unpacked, + Bits, + LongString, + ShortString, + UnsignedLongInteger, + UnsignedLongLongInteger, + UnsignedOctet, + UnsignedShortInteger, + Table, +}; +use Innmind\TimeContinuum\Clock; +use Innmind\IO\Readable\Frame; +use Innmind\Immutable\{ + Maybe, + Sequence, + Predicate\Instance, +}; /** * @psalm-immutable @@ -77,6 +94,19 @@ public static function of(int $class, int $method): self ); } + /** + * @psalm-pure + * + * @return Frame + */ + public static function frame(int $class, int $method): Frame + { + return self::maybe($class, $method)->match( + static fn($self) => Frame\NoOp::of($self), + static fn() => Frame\NoOp::of(self::connectionStart)->filter(static fn() => false), // force fail + ); + } + /** * @psalm-pure * @@ -268,4 +298,125 @@ public function equals(self $method): bool { return $method === $this; } + + /** + * @return Frame> + */ + public function incomingFrame(Clock $clock): Frame + { + /** + * @psalm-suppress NamedArgumentNotAllowed + * @var Frame> + */ + $frame = match ($this) { + Method::basicQosOk, + Method::basicRecoverOk, + Method::channelCloseOk, + Method::connectionCloseOk, + Method::exchangeDeclareOk, + Method::exchangeDeleteOk, + Method::queueBindOk, + Method::queueUnbindOk, + Method::transactionSelectOk, + Method::transactionCommitOk, + Method::transactionRollbackOk => Frame\NoOp::of(Sequence::of()), // no arguments + Method::basicConsumeOk => ShortString::frame()->map(Sequence::of(...)), // consumer tag + Method::basicCancelOk => ShortString::frame()->map(Sequence::of(...)), // consumer tag + Method::basicReturn => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + UnsignedShortInteger::frame(), // reply code + ShortString::frame(), // reply text + ShortString::frame(), // exchange + ShortString::frame(), // routing key + ), + Method::basicDeliver => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + ShortString::frame(), // consumer tag + UnsignedLongLongInteger::frame(), // delivery tag + Bits::frame(), // redelivered + ShortString::frame(), // exchange + ShortString::frame(), // routing key + ), + Method::basicGetOk => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + UnsignedLongLongInteger::frame(), // delivery tag + Bits::frame(), // redelivered + ShortString::frame(), // exchange + ShortString::frame(), // routing key + UnsignedLongInteger::frame(), // message count + ), + Method::basicGetEmpty => ShortString::frame()->map(Sequence::of(...)), // reserved, + Method::channelOpenOk => LongString::frame()->map(Sequence::of(...)), // reserved + Method::channelFlow => Bits::frame()->map(Sequence::of(...)), // active + Method::channelFlowOk => Bits::frame()->map(Sequence::of(...)), // active + Method::channelClose => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + UnsignedShortInteger::frame(), // reply code + ShortString::frame(), // reply text + UnsignedShortInteger::frame(), // failing class id + UnsignedShortInteger::frame(), // failing method id + ), + Method::connectionStart => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + UnsignedOctet::frame(), // major version + UnsignedOctet::frame(), // minor version + Table::frame($clock), // server properties + LongString::frame(), // mechanisms + LongString::frame(), // locales + ), + Method::connectionSecure => LongString::frame()->map(Sequence::of(...)), // challenge + Method::connectionTune => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + UnsignedShortInteger::frame(), // max channels + UnsignedLongInteger::frame(), // max frame size + UnsignedShortInteger::frame(), // heartbeat delay + ), + Method::connectionOpenOk => ShortString::frame()->map(Sequence::of(...)), // known hosts + Method::connectionClose => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + UnsignedShortInteger::frame(), // reply code + ShortString::frame(), // reply text + UnsignedShortInteger::frame(), // failing class id + UnsignedShortInteger::frame(), // failing method id + ), + Method::queueDeclareOk => Frame\Composite::of( + static fn(Unpacked ...$values) => Sequence::of(...$values), + ShortString::frame(), // queue + UnsignedLongInteger::frame(), // message count + UnsignedLongInteger::frame(), // consumer count + ), + Method::queuePurgeOk => UnsignedLongInteger::frame()->map(Sequence::of(...)), // message count + Method::queueDeleteOk => UnsignedLongInteger::frame()->map(Sequence::of(...)), // message count + Method::basicAck, + Method::basicCancel, + Method::basicConsume, + Method::basicGet, + Method::basicPublish, + Method::basicQos, + Method::basicRecover, + Method::basicRecoverAsync, + Method::basicReject, + Method::channelOpen, + Method::connectionOpen, + Method::connectionSecureOk, + Method::connectionStartOk, + Method::connectionTuneOk, + Method::exchangeDeclare, + Method::exchangeDelete, + Method::queueBind, + Method::queueDeclare, + Method::queueDelete, + Method::queuePurge, + Method::queueUnbind, + Method::transactionCommit, + Method::transactionRollback, + Method::transactionSelect => throw new \LogicException('Server should never send this method'), + }; + + return $frame->map( + static fn($values) => $values + ->keep(Instance::of(Unpacked::class)) + ->map(static fn($unpacked) => $unpacked->unwrap()), + ); + } } diff --git a/src/Transport/Frame/MethodClass.php b/src/Transport/Frame/MethodClass.php index b444b32..32dab8e 100644 --- a/src/Transport/Frame/MethodClass.php +++ b/src/Transport/Frame/MethodClass.php @@ -3,7 +3,7 @@ namespace Innmind\AMQP\Transport\Frame; -use Innmind\Immutable\Maybe; +use Innmind\IO\Readable\Frame; /** * @psalm-immutable @@ -21,27 +21,19 @@ enum MethodClass /** * @psalm-pure * - * @return Maybe + * @return Frame */ - public static function maybe(int $value): Maybe + public static function frame(int $value): Frame { - /** @var Maybe */ - return Maybe::of((match ($value) { - 10 => self::connection, - 20 => self::channel, - 40 => self::exchange, - 50 => self::queue, - 60 => self::basic, - 90 => self::transaction, - default => null, - })); - } - - public function toString(): string - { - return match ($this) { - self::transaction => 'tx', - default => $this->name, + /** @var Frame */ + return match ($value) { + 10 => Frame\NoOp::of(self::connection), + 20 => Frame\NoOp::of(self::channel), + 40 => Frame\NoOp::of(self::exchange), + 50 => Frame\NoOp::of(self::queue), + 60 => Frame\NoOp::of(self::basic), + 90 => Frame\NoOp::of(self::transaction), + default => Frame\NoOp::of(self::basic)->filter(static fn() => false), // force fail }; } diff --git a/src/Transport/Frame/Type.php b/src/Transport/Frame/Type.php index 5594de6..61d2700 100644 --- a/src/Transport/Frame/Type.php +++ b/src/Transport/Frame/Type.php @@ -3,7 +3,7 @@ namespace Innmind\AMQP\Transport\Frame; -use Innmind\Immutable\Maybe; +use Innmind\IO\Readable\Frame; /** * @psalm-immutable @@ -19,17 +19,16 @@ enum Type /** * @psalm-pure * - * @return Maybe + * @return Frame */ - public static function maybe(int $value): Maybe + public static function frame(int $value): Frame { - /** @var Maybe */ return match ($value) { - 1 => Maybe::just(self::method), - 2 => Maybe::just(self::header), - 3 => Maybe::just(self::body), - 8 => Maybe::just(self::heartbeat), - default => Maybe::nothing(), + 1 => Frame\NoOp::of(self::method), + 2 => Frame\NoOp::of(self::header), + 3 => Frame\NoOp::of(self::body), + 8 => Frame\NoOp::of(self::heartbeat), + default => Frame\NoOp::of(self::heartbeat)->filter(static fn() => false), // force fail }; } diff --git a/src/Transport/Frame/Value/Bits.php b/src/Transport/Frame/Value/Bits.php index 9a9cb29..2764f8c 100644 --- a/src/Transport/Frame/Value/Bits.php +++ b/src/Transport/Frame/Value/Bits.php @@ -4,11 +4,11 @@ namespace Innmind\AMQP\Transport\Frame\Value; use Innmind\AMQP\Transport\Frame\Value; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Sequence, - Maybe, + Either, }; /** @@ -38,14 +38,26 @@ public static function of(bool $first, bool ...$bits): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + return match (true) { + \is_bool($value) => Either::right(self::of($value)), + default => Either::left($value), + }; + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(1) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 1) + return Frame\Chunk::of(1) ->map( static fn($chunk) => $chunk ->map(static fn($chunk) => \decbin(\ord($chunk))) @@ -53,8 +65,9 @@ public static function unpack(Readable $stream): Maybe ->map(static fn($bit) => (bool) (int) $bit->toString()) ->reverse(), ) - ->exclude(static fn($bits) => $bits->empty()) - ->map(static fn($bits) => new self($bits)); + ->filter(static fn($bits) => !$bits->empty()) + ->map(static fn($bits) => new self($bits)) + ->map(static fn($value) => Unpacked::of(1, $value)); } /** diff --git a/src/Transport/Frame/Value/Decimal.php b/src/Transport/Frame/Value/Decimal.php index 79f11ac..c081871 100644 --- a/src/Transport/Frame/Value/Decimal.php +++ b/src/Transport/Frame/Value/Decimal.php @@ -4,11 +4,8 @@ namespace Innmind\AMQP\Transport\Frame\Value; use Innmind\AMQP\Transport\Frame\Value; -use Innmind\Stream\Readable; -use Innmind\Immutable\{ - Str, - Maybe, -}; +use Innmind\IO\Readable\Frame; +use Innmind\Immutable\Str; /** * @implements Value @@ -37,13 +34,18 @@ public static function of(int $value, int $scale): self } /** - * @return Maybe + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return UnsignedOctet::unpack($stream)->flatMap( - static fn($scale) => SignedLongInteger::unpack($stream)->map( - static fn($value) => new self($value, $scale), + return UnsignedOctet::frame()->flatMap( + static fn($scale) => SignedLongInteger::frame()->map( + static fn($value) => Unpacked::of( + $scale->read() + $value->read(), + new self($value->unwrap(), $scale->unwrap()), + ), ), ); } diff --git a/src/Transport/Frame/Value/LongString.php b/src/Transport/Frame/Value/LongString.php index 3b90279..5dd6b3d 100644 --- a/src/Transport/Frame/Value/LongString.php +++ b/src/Transport/Frame/Value/LongString.php @@ -4,10 +4,11 @@ namespace Innmind\AMQP\Transport\Frame\Value; use Innmind\AMQP\Transport\Frame\Value; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -45,20 +46,43 @@ public static function of(Str $string): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either */ - public static function unpack(Readable $stream): Maybe + public static function wrap(mixed $value): Either { - /** @psalm-suppress InvalidArgument */ - return UnsignedLongInteger::unpack($stream) - ->map(static fn($length) => $length->original()) + return Maybe::of($value) + ->filter(\is_string(...)) + ->map(Str::of(...)) + ->map(static fn($str) => $str->toEncoding(Str\Encoding::ascii)) ->flatMap( - static fn($length) => $stream - ->read($length) - ->map(static fn($string) => $string->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($string) => $string->length() === $length), + static fn($str) => UnsignedLongInteger::wrap($str->length()) + ->maybe() + ->map(static fn() => new self($str)), ) - ->map(static fn($string) => new self($string)); + ->either() + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> + */ + public static function frame(): Frame + { + return UnsignedLongInteger::frame()->flatMap( + static fn($length) => (match ($length->unwrap()->original()) { + 0 => Frame\NoOp::of(Str::of('')), + default => Frame\Chunk::of($length->unwrap()->original()), + }) + ->map(static fn($string) => new self($string)) + ->map(static fn($value) => Unpacked::of( + $length->read() + $length->unwrap()->original(), + $value, + )), + ); } public function original(): Str diff --git a/src/Transport/Frame/Value/Sequence.php b/src/Transport/Frame/Value/Sequence.php index 1217dd4..21a22ec 100644 --- a/src/Transport/Frame/Value/Sequence.php +++ b/src/Transport/Frame/Value/Sequence.php @@ -3,14 +3,19 @@ namespace Innmind\AMQP\Transport\Frame\Value; -use Innmind\AMQP\Transport\Frame\Value; +use Innmind\AMQP\Transport\{ + Frame\Value, + Protocol\ArgumentTranslator, +}; use Innmind\TimeContinuum\Clock; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Sequence as Seq, Monoid\Concat, Str, Maybe, + Either, + Predicate\Instance, }; /** @@ -42,25 +47,39 @@ public static function of(Value ...$values): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(ArgumentTranslator $translate, mixed $value): Either + { + return Maybe::of($value) + ->keep(Instance::of(Seq::class)) + ->map(static fn($values) => $values->map($translate)) + ->either() + ->map(static fn($values) => new self($values)) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Clock $clock, Readable $stream): Maybe + public static function frame(Clock $clock): Frame { - /** @var Seq */ - $values = Seq::of(); + $self = new self(Seq::of()); - return UnsignedLongInteger::unpack($stream) - ->map(static fn($length) => $length->original()) - ->flatMap(static fn($length) => match ($length) { - 0 => Maybe::just($values), + return UnsignedLongInteger::frame()->flatMap( + static fn($length) => match ($length->unwrap()->original()) { + 0 => Frame\NoOp::of(Unpacked::of($length->read(), $self)), default => self::unpackNested( $clock, - $length + $stream->position()->toInt(), - $stream, - $values, + Unpacked::of($length->read(), $self), + $length->unwrap()->original(), ), - }) - ->map(static fn($values) => new self($values)); + }, + ); } /** @@ -93,29 +112,28 @@ public function pack(): Str } /** - * @param Seq $values + * @param Unpacked $unpacked * - * @return Maybe> + * @return Frame> */ private static function unpackNested( Clock $clock, - int $boundary, - Readable $stream, - Seq $values, - ): Maybe { - return $stream - ->read(1) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 1) - ->flatMap(static fn($chunk) => Symbol::unpack($clock, $chunk->toString(), $stream)) - ->flatMap(static fn($value) => match ($stream->position()->toInt() < $boundary) { + Unpacked $unpacked, + int $length, + ): Frame { + return Frame\Chunk::of(1) + ->flatMap(static fn($chunk) => Symbol::frame($clock, $chunk->toString())) + ->map(static fn($value) => Unpacked::of( + $unpacked->read() + $value->read() + 1, + new self(($unpacked->unwrap()->original)($value->unwrap())), + )) + ->flatMap(static fn($value) => match ($value->read() < $length) { true => self::unpackNested( $clock, - $boundary, - $stream, - ($values)($value), + $value, + $length, ), - false => Maybe::just(($values)($value)), + false => Frame\NoOp::of($value), }); } } diff --git a/src/Transport/Frame/Value/ShortString.php b/src/Transport/Frame/Value/ShortString.php index 7297c8d..f4fed88 100644 --- a/src/Transport/Frame/Value/ShortString.php +++ b/src/Transport/Frame/Value/ShortString.php @@ -4,10 +4,11 @@ namespace Innmind\AMQP\Transport\Frame\Value; use Innmind\AMQP\Transport\Frame\Value; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -45,20 +46,43 @@ public static function of(Str $string): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either */ - public static function unpack(Readable $stream): Maybe + public static function wrap(mixed $value): Either { - /** @psalm-suppress InvalidArgument */ - return UnsignedOctet::unpack($stream) - ->map(static fn($length) => $length->original()) + return Maybe::of($value) + ->filter(\is_string(...)) + ->map(Str::of(...)) + ->map(static fn($str) => $str->toEncoding(Str\Encoding::ascii)) ->flatMap( - static fn($length) => $stream - ->read($length) - ->map(static fn($string) => $string->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($string) => $string->length() === $length), + static fn($str) => UnsignedOctet::wrap($str->length()) + ->maybe() + ->map(static fn() => new self($str)), ) - ->map(static fn($string) => new self($string)); + ->either() + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> + */ + public static function frame(): Frame + { + return UnsignedOctet::frame()->flatMap( + static fn($length) => (match ($length->unwrap()->original()) { + 0 => Frame\NoOp::of(Str::of('')), + default => Frame\Chunk::of($length->unwrap()->original()), + }) + ->map(static fn($string) => new self($string)) + ->map(static fn($value) => Unpacked::of( + $length->read() + $length->unwrap()->original(), + $value, + )), + ); } public function original(): Str diff --git a/src/Transport/Frame/Value/SignedLongInteger.php b/src/Transport/Frame/Value/SignedLongInteger.php index e27830d..0712eb1 100644 --- a/src/Transport/Frame/Value/SignedLongInteger.php +++ b/src/Transport/Frame/Value/SignedLongInteger.php @@ -9,10 +9,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -45,21 +46,38 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(4) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 4) + return Frame\Chunk::of(4) ->map(static function($chunk) { /** @var int<-2147483648, 2147483647> $value */ [, $value] = \unpack('l', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(4, $value)); } /** diff --git a/src/Transport/Frame/Value/SignedLongLongInteger.php b/src/Transport/Frame/Value/SignedLongLongInteger.php index 03cbdb6..cd51508 100644 --- a/src/Transport/Frame/Value/SignedLongLongInteger.php +++ b/src/Transport/Frame/Value/SignedLongLongInteger.php @@ -4,10 +4,11 @@ namespace Innmind\AMQP\Transport\Frame\Value; use Innmind\AMQP\Transport\Frame\Value; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -32,21 +33,36 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress MixedArgument */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->either() + ->map(static fn($int) => new self($int)) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(8) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 8) + return Frame\Chunk::of(8) ->map(static function($chunk) { /** @var int $value */ [, $value] = \unpack('q', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(8, $value)); } public function original(): int diff --git a/src/Transport/Frame/Value/SignedOctet.php b/src/Transport/Frame/Value/SignedOctet.php index 1232ad8..32d5d8e 100644 --- a/src/Transport/Frame/Value/SignedOctet.php +++ b/src/Transport/Frame/Value/SignedOctet.php @@ -9,10 +9,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -47,21 +48,38 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(1) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 1) + return Frame\Chunk::of(1) ->map(static function($chunk) { /** @var int<-128, 127> $value */ [, $value] = \unpack('c', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(1, $value)); } /** diff --git a/src/Transport/Frame/Value/SignedShortInteger.php b/src/Transport/Frame/Value/SignedShortInteger.php index 18f2281..3d2ec9a 100644 --- a/src/Transport/Frame/Value/SignedShortInteger.php +++ b/src/Transport/Frame/Value/SignedShortInteger.php @@ -9,10 +9,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -45,21 +46,38 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(2) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 2) + return Frame\Chunk::of(2) ->map(static function($chunk) { /** @var int<-32768, 32767> $value */ [, $value] = \unpack('s', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(2, $value)); } /** diff --git a/src/Transport/Frame/Value/Symbol.php b/src/Transport/Frame/Value/Symbol.php index 12ba28c..fe68e05 100644 --- a/src/Transport/Frame/Value/Symbol.php +++ b/src/Transport/Frame/Value/Symbol.php @@ -3,13 +3,9 @@ namespace Innmind\AMQP\Transport\Frame\Value; -use Innmind\AMQP\Transport\Frame\Value; use Innmind\TimeContinuum\Clock; -use Innmind\Stream\Readable; -use Innmind\Immutable\{ - Str, - Maybe, -}; +use Innmind\IO\Readable\Frame; +use Innmind\Immutable\Str; /** * @psalm-immutable @@ -34,31 +30,30 @@ enum Symbol case table; /** - * @return Maybe + * @psalm-pure + * + * @return Frame */ - public static function unpack( - Clock $clock, - string $symbol, - Readable $stream, - ): Maybe { - /** @var Maybe */ + public static function frame(Clock $clock, string $symbol): Frame + { + /** @var Frame */ return match ($symbol) { - 'b' => SignedOctet::unpack($stream), - 'B' => UnsignedOctet::unpack($stream), - 'U' => SignedShortInteger::unpack($stream), - 'u' => UnsignedShortInteger::unpack($stream), - 'I' => SignedLongInteger::unpack($stream), - 'i' => UnsignedLongInteger::unpack($stream), - 'L' => SignedLongLongInteger::unpack($stream), - 'l' => UnsignedLongLongInteger::unpack($stream), - 'D' => Decimal::unpack($stream), - 'T' => Timestamp::unpack($clock, $stream), - 'V' => VoidValue::unpack($stream), - 't' => Bits::unpack($stream), - 's' => ShortString::unpack($stream), - 'S' => LongString::unpack($stream), - 'A' => Sequence::unpack($clock, $stream), - 'F' => Table::unpack($clock, $stream), + 'b' => SignedOctet::frame(), + 'B' => UnsignedOctet::frame(), + 'U' => SignedShortInteger::frame(), + 'u' => UnsignedShortInteger::frame(), + 'I' => SignedLongInteger::frame(), + 'i' => UnsignedLongInteger::frame(), + 'L' => SignedLongLongInteger::frame(), + 'l' => UnsignedLongLongInteger::frame(), + 'D' => Decimal::frame(), + 'T' => Timestamp::frame($clock), + 'V' => VoidValue::frame(), + 't' => Bits::frame(), + 's' => ShortString::frame(), + 'S' => LongString::frame(), + 'A' => Sequence::frame($clock), + 'F' => Table::frame($clock), }; } diff --git a/src/Transport/Frame/Value/Table.php b/src/Transport/Frame/Value/Table.php index 4807d02..5101eea 100644 --- a/src/Transport/Frame/Value/Table.php +++ b/src/Transport/Frame/Value/Table.php @@ -3,15 +3,20 @@ namespace Innmind\AMQP\Transport\Frame\Value; -use Innmind\AMQP\Transport\Frame\Value; +use Innmind\AMQP\Transport\{ + Frame\Value, + Protocol\ArgumentTranslator, +}; use Innmind\TimeContinuum\Clock; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Sequence as Seq, + Maybe, + Either, Map, Monoid\Concat, - Maybe, + Predicate\Instance, }; /** @@ -42,25 +47,51 @@ public static function of(Map $map): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(ArgumentTranslator $translate, mixed $value): Either + { + /** @psalm-suppress MixedArgumentTypeCoercion */ + return Maybe::of($value) + ->keep(Instance::of(Map::class)) + ->flatMap( + static fn($map) => $map->reduce( + Maybe::just(Map::of()), + static fn(Maybe $translated, $key, $value) => $translated->flatMap( + static fn(Map $map) => ShortString::wrap($key) + ->maybe() + ->map( + static fn() => ($map)($key, $translate($value)), + ), + ), + ), + ) + ->either() + ->map(static fn($map) => new self($map)) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Clock $clock, Readable $stream): Maybe + public static function frame(Clock $clock): Frame { - /** @var Map */ - $values = Map::of(); + $self = new self(Map::of()); - return UnsignedLongInteger::unpack($stream) - ->map(static fn($length) => $length->original()) - ->flatMap(static fn($length) => match ($length) { - 0 => Maybe::just($values), + return UnsignedLongInteger::frame()->flatMap( + static fn($length) => match ($length->unwrap()->original()) { + 0 => Frame\NoOp::of(Unpacked::of($length->read(), $self)), default => self::unpackNested( $clock, - $length + $stream->position()->toInt(), - $stream, - $values, + Unpacked::of($length->read(), $self), + $length->unwrap()->original(), ), - }) - ->map(static fn($map) => new self($map)); + }, + ); } /** @@ -98,38 +129,39 @@ public function pack(): Str } /** - * @param Map $values + * @psalm-pure + * + * @param Unpacked $unpacked * - * @return Maybe> + * @return Frame> */ private static function unpackNested( Clock $clock, - int $boundary, - Readable $stream, - Map $values, - ): Maybe { - return ShortString::unpack($stream) - ->map(static fn($key) => $key->original()->toString()) + Unpacked $unpacked, + int $length, + ): Frame { + return ShortString::frame() ->flatMap( - static fn($key) => $stream - ->read(1) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 1) - ->flatMap(static fn($chunk) => Symbol::unpack( + static fn($key) => Frame\Chunk::of(1) + ->flatMap(static fn($chunk) => Symbol::frame( $clock, $chunk->toString(), - $stream, )) - ->map(static fn($value) => ($values)($key, $value)), + ->map(static fn($value) => Unpacked::of( + $unpacked->read() + $key->read() + $value->read() + 1, + new self(($unpacked->unwrap()->original)( + $key->unwrap()->original()->toString(), + $value->unwrap(), + )), + )), ) - ->flatMap(static fn($values) => match ($stream->position()->toInt() < $boundary) { + ->flatMap(static fn($value) => match ($value->read() < $length) { true => self::unpackNested( $clock, - $boundary, - $stream, - $values, + $value, + $length, ), - false => Maybe::just($values), + false => Frame\NoOp::of($value), }); } } diff --git a/src/Transport/Frame/Value/Timestamp.php b/src/Transport/Frame/Value/Timestamp.php index fcb5bb3..38b8745 100644 --- a/src/Transport/Frame/Value/Timestamp.php +++ b/src/Transport/Frame/Value/Timestamp.php @@ -11,10 +11,12 @@ Clock, PointInTime, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, + Predicate\Instance, }; /** @@ -39,14 +41,42 @@ public static function of(PointInTime $point): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + return Maybe::of($value) + ->keep(Instance::of(PointInTime::class)) + ->either() + ->map(static fn($point) => new self($point)) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Clock $clock, Readable $stream): Maybe + public static function frame(Clock $clock): Frame { - return UnsignedLongLongInteger::unpack($stream) - ->map(static fn($time) => $time->original()) - ->flatMap(static fn($time) => $clock->at((string) $time, new TimestampFormat)) - ->map(static fn($point) => new self($point)); + return UnsignedLongLongInteger::frame()->flatMap( + static fn($time) => $clock + ->at((string) $time->unwrap()->original(), new TimestampFormat) + ->map(static fn($point) => new self($point)) + ->map(static fn($value) => Unpacked::of( + $time->read(), + $value, + )) + ->match( + static fn($unpacked) => Frame\NoOp::of($unpacked), + static fn() => Frame\NoOp::of(Unpacked::of( + 0, + new self($clock->now()), + ))->filter(static fn() => false), // to force failing since the read time is invalid + ), + ); } public function original(): PointInTime diff --git a/src/Transport/Frame/Value/Unpacked.php b/src/Transport/Frame/Value/Unpacked.php new file mode 100644 index 0000000..fe28b26 --- /dev/null +++ b/src/Transport/Frame/Value/Unpacked.php @@ -0,0 +1,59 @@ +read = $read; + $this->value = $value; + } + + /** + * @psalm-pure + * @template V of Value + * + * @param 0|positive-int $read + * @param V $value + * + * @return self + */ + public static function of(int $read, Value $value): self + { + return new self($read, $value); + } + + /** + * @return 0|positive-int + */ + public function read(): int + { + return $this->read; + } + + /** + * @return T + */ + public function unwrap(): Value + { + return $this->value; + } +} diff --git a/src/Transport/Frame/Value/UnsignedLongInteger.php b/src/Transport/Frame/Value/UnsignedLongInteger.php index f6b678e..43aa1cf 100644 --- a/src/Transport/Frame/Value/UnsignedLongInteger.php +++ b/src/Transport/Frame/Value/UnsignedLongInteger.php @@ -9,10 +9,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -56,21 +57,38 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(4) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 4) + return Frame\Chunk::of(4) ->map(static function($chunk) { /** @var int<0, 4294967295> $value */ [, $value] = \unpack('N', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(4, $value)); } /** diff --git a/src/Transport/Frame/Value/UnsignedLongLongInteger.php b/src/Transport/Frame/Value/UnsignedLongLongInteger.php index c216a2a..3642ba2 100644 --- a/src/Transport/Frame/Value/UnsignedLongLongInteger.php +++ b/src/Transport/Frame/Value/UnsignedLongLongInteger.php @@ -10,10 +10,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -57,21 +58,38 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(8) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 8) + return Frame\Chunk::of(8) ->map(static function($chunk) { /** @var int<0, max> $value */ [, $value] = \unpack('J', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(8, $value)); } /** diff --git a/src/Transport/Frame/Value/UnsignedOctet.php b/src/Transport/Frame/Value/UnsignedOctet.php index b1ce74b..c8d9410 100644 --- a/src/Transport/Frame/Value/UnsignedOctet.php +++ b/src/Transport/Frame/Value/UnsignedOctet.php @@ -9,10 +9,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -58,21 +59,38 @@ public static function of(int $octet): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(1) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 1) + return Frame\Chunk::of(1) ->map(static function($chunk) { /** @var int<0, 255> $octet */ [, $octet] = \unpack('C', $chunk->toString()); return $octet; }) - ->map(static fn($octet) => new self($octet)); + ->map(static fn($octet) => new self($octet)) + ->map(static fn($value) => Unpacked::of(1, $value)); } /** diff --git a/src/Transport/Frame/Value/UnsignedShortInteger.php b/src/Transport/Frame/Value/UnsignedShortInteger.php index cff1dd6..02650a7 100644 --- a/src/Transport/Frame/Value/UnsignedShortInteger.php +++ b/src/Transport/Frame/Value/UnsignedShortInteger.php @@ -9,10 +9,11 @@ DefinitionSet\Set, DefinitionSet\Range, }; -use Innmind\Stream\Readable; +use Innmind\IO\Readable\Frame; use Innmind\Immutable\{ Str, Maybe, + Either, }; /** @@ -56,21 +57,38 @@ public static function of(int $value): self } /** - * @return Maybe + * @psalm-pure + * + * @return Either + */ + public static function wrap(mixed $value): Either + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Maybe::of($value) + ->filter(\is_int(...)) + ->map(Integer::of(...)) + ->filter(self::definitionSet()->contains(...)) + ->either() + ->map(static fn($int) => new self($int->value())) + ->leftMap(static fn(): mixed => $value); + } + + /** + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return $stream - ->read(2) - ->map(static fn($chunk) => $chunk->toEncoding(Str\Encoding::ascii)) - ->filter(static fn($chunk) => $chunk->length() === 2) + return Frame\Chunk::of(2) ->map(static function($chunk) { /** @var int<0, 65535> $value */ [, $value] = \unpack('n', $chunk->toString()); return $value; }) - ->map(static fn($value) => new self($value)); + ->map(static fn($value) => new self($value)) + ->map(static fn($value) => Unpacked::of(2, $value)); } /** diff --git a/src/Transport/Frame/Value/VoidValue.php b/src/Transport/Frame/Value/VoidValue.php index a702a6d..172d56d 100644 --- a/src/Transport/Frame/Value/VoidValue.php +++ b/src/Transport/Frame/Value/VoidValue.php @@ -4,11 +4,8 @@ namespace Innmind\AMQP\Transport\Frame\Value; use Innmind\AMQP\Transport\Frame\Value; -use Innmind\Stream\Readable; -use Innmind\Immutable\{ - Str, - Maybe, -}; +use Innmind\IO\Readable\Frame; +use Innmind\Immutable\Str; /** * @implements Value @@ -17,11 +14,13 @@ final class VoidValue implements Value { /** - * @return Maybe + * @psalm-pure + * + * @return Frame> */ - public static function unpack(Readable $stream): Maybe + public static function frame(): Frame { - return Maybe::just(new self); + return Frame\NoOp::of(Unpacked::of(0, new self)); } public function original(): void diff --git a/src/Transport/Frame/Visitor/ChunkArguments.php b/src/Transport/Frame/Visitor/ChunkArguments.php deleted file mode 100644 index 9c08abe..0000000 --- a/src/Transport/Frame/Visitor/ChunkArguments.php +++ /dev/null @@ -1,69 +0,0 @@ -> */ - private Sequence $types; - - /** - * @no-named-arguments - * - * @param list> $types - */ - public function __construct(callable ...$types) - { - $this->types = Sequence::of(...$types); - } - - /** - * @return Maybe> - */ - public function __invoke(Readable $arguments): Maybe - { - /** @var Sequence */ - $values = Sequence::of(); - - /** @psalm-suppress MixedArgumentTypeCoercion */ - return $this - ->types - ->reduce( - Maybe::just($values), - fn(Maybe $maybe, $unpack) => $this->unpack( - $maybe, - $unpack, - $arguments, - ), - ); - } - - /** - * @param Maybe> $maybe - * @param callable(Readable): Maybe $unpack - * - * @return Maybe> - */ - private function unpack( - Maybe $maybe, - callable $unpack, - Readable $arguments, - ): Maybe { - return $maybe->flatMap( - static fn($values) => $unpack($arguments)->map( - static fn($value) => ($values)($value), - ), - ); - } -} diff --git a/src/Transport/Protocol.php b/src/Transport/Protocol.php index 09d8a42..1dc6ba3 100644 --- a/src/Transport/Protocol.php +++ b/src/Transport/Protocol.php @@ -12,9 +12,7 @@ Protocol\Basic, Protocol\Transaction, Protocol\ArgumentTranslator, - Protocol\Reader, Frame\Method, - Frame\Visitor\ChunkArguments, Frame\Value\UnsignedOctet, Frame\Value\UnsignedShortInteger, Frame\Value\UnsignedLongLongInteger, @@ -24,11 +22,8 @@ Frame\Value, }; use Innmind\TimeContinuum\Clock; -use Innmind\Stream\Readable; -use Innmind\Immutable\{ - Sequence, - Maybe, -}; +use Innmind\IO\Readable\Frame; +use Innmind\Immutable\Sequence; /** * @internal @@ -37,7 +32,6 @@ final class Protocol { private Clock $clock; private Version $version; - private Reader $read; private Connection $connection; private Channel $channel; private Exchange $exchange; @@ -49,7 +43,6 @@ public function __construct(Clock $clock, ArgumentTranslator $translator) { $this->clock = $clock; $this->version = Version::v091; - $this->read = new Reader($clock); $this->connection = new Connection; $this->channel = new Channel; $this->exchange = new Exchange($translator); @@ -64,21 +57,28 @@ public function version(): Version } /** - * @return Maybe> + * @psalm-mutation-free + * + * @return Frame> */ - public function read(Method $method, Readable $arguments): Maybe + public function frame(Method $method): Frame { - return ($this->read)($method, $arguments); + return $method->incomingFrame($this->clock); } /** - * @return Maybe> + * @psalm-mutation-free + * + * @return Frame> */ - public function readHeader(Readable $arguments): Maybe + public function headerFrame(): Frame { - return UnsignedLongLongInteger::unpack($arguments)->flatMap( - fn($bodySize) => UnsignedShortInteger::unpack($arguments)->flatMap( - fn($flags) => $this->parseHeader($bodySize, $flags, $arguments), + return UnsignedLongLongInteger::frame()->flatMap( + fn($bodySize) => UnsignedShortInteger::frame()->flatMap( + fn($flags) => $this->parseHeader( + $bodySize->unwrap(), + $flags->unwrap(), + ), ), ); } @@ -114,41 +114,45 @@ public function transaction(): Transaction } /** - * @return Maybe> + * @psalm-mutation-free + * + * @return Frame> */ private function parseHeader( UnsignedLongLongInteger $bodySize, UnsignedShortInteger $flags, - Readable $arguments, - ): Maybe { + ): Frame { $flagBits = $flags->original(); $toChunk = Sequence::of( - [15, ShortString::unpack(...)], // content type - [14, ShortString::unpack(...)], // content encoding - [13, fn(Readable $stream) => Table::unpack($this->clock, $stream)], // headers - [12, UnsignedOctet::unpack(...)], // delivery mode - [11, UnsignedOctet::unpack(...)], // priority - [10, ShortString::unpack(...)], // correlation id - [9, ShortString::unpack(...)], // reply to - [8, ShortString::unpack(...)], // expiration - [7, ShortString::unpack(...)], // id, - [6, fn(Readable $stream) => Timestamp::unpack($this->clock, $stream)], // timestamp - [5, ShortString::unpack(...)], // type - [4, ShortString::unpack(...)], // user id - [3, ShortString::unpack(...)], // app id + [15, ShortString::frame()], // content type + [14, ShortString::frame()], // content encoding + [13, Table::frame($this->clock)], // headers + [12, UnsignedOctet::frame()], // delivery mode + [11, UnsignedOctet::frame()], // priority + [10, ShortString::frame()], // correlation id + [9, ShortString::frame()], // reply to + [8, ShortString::frame()], // expiration + [7, ShortString::frame()], // id, + [6, Timestamp::frame($this->clock)], // timestamp + [5, ShortString::frame()], // type + [4, ShortString::frame()], // user id + [3, ShortString::frame()], // app id ) ->map(static fn($pair) => [1 << $pair[0], $pair[1]]) ->filter(static fn($pair) => (bool) ($flagBits & $pair[0])) ->map(static fn($pair) => $pair[1]) - ->toList(); + ->map(static fn($frame) => $frame->map( + static fn($value) => $value->unwrap(), + )); - /** - * @psalm-suppress InvalidArgument - * @psalm-suppress ArgumentTypeCoercion - * @var Maybe> - */ - return (new ChunkArguments(...$toChunk))($arguments)->map( - static fn($arguments) => Sequence::of($bodySize, $flags)->append($arguments), + /** @var Frame> */ + return $toChunk->match( + static fn($first, $rest) => Frame\Composite::of( + static fn(Value ...$values) => Sequence::of($bodySize, $flags, ...$values), + $first, + ...$rest->toList(), + ), + static fn() => Frame\NoOp::of(Sequence::of($bodySize, $flags)), ); } } diff --git a/src/Transport/Protocol/ArgumentTranslator.php b/src/Transport/Protocol/ArgumentTranslator.php index 38fc34f..9e40ce6 100644 --- a/src/Transport/Protocol/ArgumentTranslator.php +++ b/src/Transport/Protocol/ArgumentTranslator.php @@ -8,10 +8,32 @@ Exception\ValueNotTranslatable, }; -interface ArgumentTranslator +final class ArgumentTranslator { - /** - * @throws ValueNotTranslatable - */ - public function __invoke(mixed $value): Value; + public function __invoke(mixed $value): Value + { + if ($value instanceof Value) { + return $value; + } + + // TODO find a way to support decimals + return Value\Bits::wrap($value) + ->otherwise(Value\ShortString::wrap(...)) + ->otherwise(Value\LongString::wrap(...)) + ->otherwise(Value\UnsignedOctet::wrap(...)) + ->otherwise(Value\UnsignedShortInteger::wrap(...)) + ->otherwise(Value\UnsignedLongInteger::wrap(...)) + ->otherwise(Value\UnsignedLongLongInteger::wrap(...)) + ->otherwise(Value\SignedOctet::wrap(...)) + ->otherwise(Value\SignedShortInteger::wrap(...)) + ->otherwise(Value\SignedLongInteger::wrap(...)) + ->otherwise(Value\SignedLongLongInteger::wrap(...)) + ->otherwise(Value\Timestamp::wrap(...)) + ->otherwise(fn($value) => Value\Sequence::wrap($this, $value)) + ->otherwise(fn($value) => Value\Table::wrap($this, $value)) + ->match( + static fn($value) => $value, + static fn($value) => throw new ValueNotTranslatable($value), + ); + } } diff --git a/src/Transport/Protocol/ArgumentTranslator/Delegate.php b/src/Transport/Protocol/ArgumentTranslator/Delegate.php deleted file mode 100644 index 5e867c9..0000000 --- a/src/Transport/Protocol/ArgumentTranslator/Delegate.php +++ /dev/null @@ -1,37 +0,0 @@ - */ - private array $translators; - - /** - * @no-named-arguments - */ - public function __construct(ArgumentTranslator ...$translators) - { - $this->translators = $translators; - } - - public function __invoke(mixed $value): Value - { - foreach ($this->translators as $translate) { - try { - return $translate($value); - } catch (ValueNotTranslatable $e) { - // pass - } - } - - throw new ValueNotTranslatable($value); - } -} diff --git a/src/Transport/Protocol/ArgumentTranslator/ValueTranslator.php b/src/Transport/Protocol/ArgumentTranslator/ValueTranslator.php deleted file mode 100644 index cfe95f9..0000000 --- a/src/Transport/Protocol/ArgumentTranslator/ValueTranslator.php +++ /dev/null @@ -1,22 +0,0 @@ -mandatory(), $command->immediate(), ), - ); - yield Frame::header( + ), + Frame::header( $channel, MethodClass::basic, UnsignedLongLongInteger::of($command->message()->length()), ...$this->serializeProperties($command->message()), - ); - }); + ), + ); return $frames->append( $maxFrameSize diff --git a/src/Transport/Protocol/Channel.php b/src/Transport/Protocol/Channel.php index 55afb22..eed6ac9 100644 --- a/src/Transport/Protocol/Channel.php +++ b/src/Transport/Protocol/Channel.php @@ -10,7 +10,6 @@ Transport\Frame, Transport\Frame\Method, Transport\Frame\Channel as FrameChannel, - Transport\Frame\Type, Transport\Frame\Value\ShortString, Transport\Frame\Value\Bits, Transport\Frame\Value\UnsignedShortInteger, diff --git a/src/Transport/Protocol/Connection.php b/src/Transport/Protocol/Connection.php index 3f1f349..171275a 100644 --- a/src/Transport/Protocol/Connection.php +++ b/src/Transport/Protocol/Connection.php @@ -10,7 +10,6 @@ Model\Connection\Open, Model\Connection\Close, Transport\Frame, - Transport\Frame\Type, Transport\Frame\Channel, Transport\Frame\Method, Transport\Frame\Value, diff --git a/src/Transport/Protocol/Exchange.php b/src/Transport/Protocol/Exchange.php index 7a2b04f..8916851 100644 --- a/src/Transport/Protocol/Exchange.php +++ b/src/Transport/Protocol/Exchange.php @@ -8,9 +8,7 @@ Model\Exchange\Deletion, Transport\Frame, Transport\Frame\Channel as FrameChannel, - Transport\Frame\Type, Transport\Frame\Method, - Transport\Frame\Value, Transport\Frame\Value\UnsignedShortInteger, Transport\Frame\Value\ShortString, Transport\Frame\Value\Bits, @@ -18,7 +16,6 @@ }; use Innmind\Immutable\{ Str, - Map, Sequence, }; diff --git a/src/Transport/Protocol/Queue.php b/src/Transport/Protocol/Queue.php index a967441..1f6edaf 100644 --- a/src/Transport/Protocol/Queue.php +++ b/src/Transport/Protocol/Queue.php @@ -11,9 +11,7 @@ Model\Queue\Purge, Transport\Frame, Transport\Frame\Channel as FrameChannel, - Transport\Frame\Type, Transport\Frame\Method, - Transport\Frame\Value, Transport\Frame\Value\UnsignedShortInteger, Transport\Frame\Value\ShortString, Transport\Frame\Value\Bits, diff --git a/src/Transport/Protocol/Reader.php b/src/Transport/Protocol/Reader.php deleted file mode 100644 index 5df4547..0000000 --- a/src/Transport/Protocol/Reader.php +++ /dev/null @@ -1,325 +0,0 @@ -clock = $clock; - } - - /** - * @return Maybe> - */ - public function __invoke(Method $method, Readable $arguments): Maybe - { - $chunk = match ($method) { - Method::basicQosOk => $this->basicQosOk(), - Method::basicConsumeOk => $this->basicConsumeOk(), - Method::basicCancelOk => $this->basicCancelOk(), - Method::basicReturn => $this->basicReturn(), - Method::basicDeliver => $this->basicDeliver(), - Method::basicGetOk => $this->basicGetOk(), - Method::basicGetEmpty => $this->basicGetEmpty(), - Method::basicRecoverOk => $this->basicRecoverOk(), - Method::channelOpenOk => $this->channelOpenOk(), - Method::channelFlow => $this->channelFlow(), - Method::channelFlowOk => $this->channelFlowOk(), - Method::channelClose => $this->channelClose(), - Method::channelCloseOk => $this->channelCloseOk(), - Method::connectionStart => $this->connectionStart(), - Method::connectionSecure => $this->connectionSecure(), - Method::connectionTune => $this->connectionTune(), - Method::connectionOpenOk => $this->connectionOpenOk(), - Method::connectionClose => $this->connectionClose(), - Method::connectionCloseOk => $this->connectionCloseOk(), - Method::exchangeDeclareOk => $this->exchangeDeclareOk(), - Method::exchangeDeleteOk => $this->exchangeDeleteOk(), - Method::queueDeclareOk => $this->queueDeclareOk(), - Method::queueBindOk => $this->queueBindOk(), - Method::queueUnbindOk => $this->queueUnbindOk(), - Method::queuePurgeOk => $this->queuePurgeOk(), - Method::queueDeleteOk => $this->queueDeleteOk(), - Method::transactionSelectOk => $this->transactionSelectOk(), - Method::transactionCommitOk => $this->transactionCommitOk(), - Method::transactionRollbackOk => $this->transactionRollbackOk(), - Method::basicAck, - Method::basicCancel, - Method::basicConsume, - Method::basicGet, - Method::basicPublish, - Method::basicQos, - Method::basicRecover, - Method::basicRecoverAsync, - Method::basicReject, - Method::channelOpen, - Method::connectionOpen, - Method::connectionSecureOk, - Method::connectionStartOk, - Method::connectionTuneOk, - Method::exchangeDeclare, - Method::exchangeDelete, - Method::queueBind, - Method::queueDeclare, - Method::queueDelete, - Method::queuePurge, - Method::queueUnbind, - Method::transactionCommit, - Method::transactionRollback, - Method::transactionSelect => throw new \LogicException('Server should never send this method'), - }; - - return $chunk($arguments); - } - - private function basicQosOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function basicConsumeOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - ShortString::unpack(...), // consumer tag - ); - } - - private function basicCancelOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - ShortString::unpack(...), // consumer tag - ); - } - - private function basicReturn(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedShortInteger::unpack(...), // reply code - ShortString::unpack(...), // reply text - ShortString::unpack(...), // exchange - ShortString::unpack(...), // routing key - ); - } - - private function basicDeliver(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - ShortString::unpack(...), // consumer tag - UnsignedLongLongInteger::unpack(...), // delivery tag - Bits::unpack(...), // redelivered - ShortString::unpack(...), // exchange - ShortString::unpack(...), // routing key - ); - } - - private function basicGetOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedLongLongInteger::unpack(...), // delivery tag - Bits::unpack(...), // redelivered - ShortString::unpack(...), // exchange - ShortString::unpack(...), // routing key - UnsignedLongInteger::unpack(...), // message count - ); - } - - private function basicGetEmpty(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - ShortString::unpack(...), // reserved - ); - } - - private function basicRecoverOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function channelOpenOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - LongString::unpack(...), // reserved - ); - } - - private function channelFlow(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - Bits::unpack(...), // active - ); - } - - private function channelFlowOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - Bits::unpack(...), // active - ); - } - - private function channelClose(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedShortInteger::unpack(...), // reply code - ShortString::unpack(...), // reply text - UnsignedShortInteger::unpack(...), // failing class id - UnsignedShortInteger::unpack(...), // failing method id - ); - } - - private function channelCloseOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function connectionStart(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedOctet::unpack(...), // major version - UnsignedOctet::unpack(...), // minor version - fn(Readable $stream) => Table::unpack($this->clock, $stream), // server properties - LongString::unpack(...), // mechanisms - LongString::unpack(...), // locales - ); - } - - private function connectionSecure(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - LongString::unpack(...), // challenge - ); - } - - private function connectionTune(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedShortInteger::unpack(...), // max channels - UnsignedLongInteger::unpack(...), // max frame size - UnsignedShortInteger::unpack(...), // heartbeat delay - ); - } - - private function connectionOpenOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - ShortString::unpack(...), // known hosts - ); - } - - private function connectionClose(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedShortInteger::unpack(...), // reply code - ShortString::unpack(...), // reply text - UnsignedShortInteger::unpack(...), // failing class id - UnsignedShortInteger::unpack(...), // failing method id - ); - } - - private function connectionCloseOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function exchangeDeclareOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function exchangeDeleteOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function queueDeclareOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - ShortString::unpack(...), // queue - UnsignedLongInteger::unpack(...), // message count - UnsignedLongInteger::unpack(...), // consumer count - ); - } - - private function queueBindOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function queueUnbindOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function queuePurgeOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedLongInteger::unpack(...), // message count - ); - } - - private function queueDeleteOk(): ChunkArguments - { - /** @psalm-suppress InvalidArgument Because it doesn't understand it accepts subtypes */ - return new ChunkArguments( - UnsignedLongInteger::unpack(...), // message count - ); - } - - private function transactionSelectOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function transactionCommitOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } - - private function transactionRollbackOk(): ChunkArguments - { - return new ChunkArguments; // no arguments - } -} diff --git a/src/Transport/Protocol/Transaction.php b/src/Transport/Protocol/Transaction.php index ba38733..f5019e1 100644 --- a/src/Transport/Protocol/Transaction.php +++ b/src/Transport/Protocol/Transaction.php @@ -4,12 +4,8 @@ namespace Innmind\AMQP\Transport\Protocol; use Innmind\AMQP\{ - Model\Transaction\Select, - Model\Transaction\Commit, - Model\Transaction\Rollback, Transport\Frame, Transport\Frame\Channel as FrameChannel, - Transport\Frame\Type, Transport\Frame\Method, }; use Innmind\Immutable\Sequence; diff --git a/src/Transport/ReceivedFrame.php b/src/Transport/ReceivedFrame.php index 68773ab..281d667 100644 --- a/src/Transport/ReceivedFrame.php +++ b/src/Transport/ReceivedFrame.php @@ -8,28 +8,26 @@ */ final class ReceivedFrame { - private Connection $connection; private Frame $frame; - private function __construct(Connection $connection, Frame $frame) + private function __construct(Frame $frame) { - $this->connection = $connection; $this->frame = $frame; } - public static function of(Connection $connection, Frame $frame): self + public static function of(Frame $frame): self { - return new self($connection, $frame); + return new self($frame); } - public function connection(): Connection + public function frame(): Frame { - return $this->connection; + return $this->frame; } - public function frame(): Frame + public function is(Frame\Method $method): bool { - return $this->frame; + return $this->frame->is($method); } public function oneOf(Frame\Method ...$methods): bool diff --git a/src/Transport/ReceivedMessage.php b/src/Transport/ReceivedMessage.php deleted file mode 100644 index 781ff26..0000000 --- a/src/Transport/ReceivedMessage.php +++ /dev/null @@ -1,36 +0,0 @@ -connection = $connection; - $this->message = $message; - } - - public static function of(Connection $connection, Message $message): self - { - return new self($connection, $message); - } - - public function connection(): Connection - { - return $this->connection; - } - - public function message(): Message - { - return $this->message; - } -} diff --git a/tests/Transport/Connection/FrameReaderTest.php b/tests/Transport/Connection/FrameReaderTest.php index 3a34fe6..71274d4 100644 --- a/tests/Transport/Connection/FrameReaderTest.php +++ b/tests/Transport/Connection/FrameReaderTest.php @@ -9,17 +9,15 @@ Transport\Frame\Type, Transport\Frame\Channel, Transport\Frame\Method, - Transport\Frame\Value, Transport\Frame\Value\UnsignedOctet, Transport\Frame\Value\Table, Transport\Frame\Value\LongString, - Transport\Frame\Value\Text, Transport\Frame\Value\ShortString, Transport\Frame\Value\UnsignedLongLongInteger, Transport\Frame\Value\UnsignedShortInteger, Transport\Frame\Value\Timestamp, Transport\Protocol, - Transport\Protocol\ArgumentTranslator\ValueTranslator, + Transport\Protocol\ArgumentTranslator, Model\Basic\Publish, Model\Basic\Message, Model\Basic\Message\AppId, @@ -35,9 +33,10 @@ Model\Connection\MaxFrameSize, TimeContinuum\Format\Timestamp as TimestampFormat, }; +use Innmind\IO\IO; use Innmind\Stream\{ Readable\Stream, - Readable, + Watch\Select, }; use Innmind\TimeContinuum\Earth\{ ElapsedPeriod, @@ -56,13 +55,11 @@ class FrameReaderTest extends TestCase public function setUp(): void { - $this->protocol = new Protocol(new Clock, new ValueTranslator); + $this->protocol = new Protocol(new Clock, new ArgumentTranslator); } public function testReadCommand() { - $read = new FrameReader; - $file = \tmpfile(); \fwrite( $file, @@ -77,20 +74,23 @@ public function testReadCommand() )->pack()->toString(), ); \fseek($file, 0); - $stream = Stream::of($file); - $frame = $read($stream, $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - ); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); $this->assertInstanceOf(Frame::class, $frame); } public function testReturnNothingWhenFrameEndMarkerInvalid() { - $read = new FrameReader; - $file = \tmpfile(); $frame = Frame::method( new Channel(0), @@ -105,18 +105,23 @@ public function testReturnNothingWhenFrameEndMarkerInvalid() $frame .= (UnsignedOctet::of(0xCD))->pack()->toString(); \fwrite($file, $frame); \fseek($file, 0); - $stream = Stream::of($file); - $this->assertNull($read($stream, $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - )); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); + + $this->assertNull($frame); } public function testReturnNothingWhenPayloadTooShort() { - $read = new FrameReader; - $file = \tmpfile(); $frame = Frame::method( new Channel(0), @@ -125,12 +130,19 @@ public function testReturnNothingWhenPayloadTooShort() $frame = \mb_substr($frame, 0, -2, 'ASCII'); \fwrite($file, $frame); \fseek($file, 0); - $stream = Stream::of($file); - $this->assertNull($read($stream, $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - )); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); + + $this->assertNull($frame); } public function testReturnNothingWhenNoFrameDeteted() @@ -138,12 +150,19 @@ public function testReturnNothingWhenNoFrameDeteted() $file = \tmpfile(); \fwrite($file, $content = "AMQP\x00\x00\x09\x01"); \fseek($file, 0); - $stream = Stream::of($file); - $this->assertNull((new FrameReader)($stream, $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - )); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); + + $this->assertNull($frame); } public function testReadHeader() @@ -182,10 +201,16 @@ public function testReadHeader() \fwrite($file, $header->pack()->toString()); \fseek($file, 0); - $frame = (new FrameReader)(Stream::of($file), $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - ); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); $this->assertInstanceOf(Frame::class, $frame); $this->assertSame(Type::header, $frame->type()); @@ -435,10 +460,16 @@ public function testReadBody() )->pack()->toString()); \fseek($file, 0); - $frame = (new FrameReader)(Stream::of($file), $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - ); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); $this->assertInstanceOf(Frame::class, $frame); $this->assertSame(Type::body, $frame->type()); @@ -456,10 +487,16 @@ public function testReadHeartbeat() \fwrite($file, Frame::heartbeat()->pack()->toString()); \fseek($file, 0); - $frame = (new FrameReader)(Stream::of($file), $this->protocol)->match( - static fn($frame) => $frame, - static fn() => null, - ); + $frame = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::of($file)) + ->toEncoding(Str\Encoding::ascii) + ->frames((new FrameReader)($this->protocol)) + ->one() + ->match( + static fn($frame) => $frame, + static fn() => null, + ); $this->assertInstanceOf(Frame::class, $frame); $this->assertSame(Type::heartbeat, $frame->type()); diff --git a/tests/Transport/ConnectionTest.php b/tests/Transport/ConnectionTest.php index e90e3e3..4fdd910 100644 --- a/tests/Transport/ConnectionTest.php +++ b/tests/Transport/ConnectionTest.php @@ -10,7 +10,6 @@ Transport\Frame, Transport\Frame\Channel, Transport\Frame\Method, - Model\Connection\MaxFrameSize, Failure, }; use Innmind\Socket\Internet\Transport; @@ -31,7 +30,7 @@ public function testInterface() $connection = Connection::open( Transport::tcp(), Url::of('//guest:guest@localhost:5672/'), - $protocol = new Protocol($os->clock(), $this->createMock(ArgumentTranslator::class)), + $protocol = new Protocol($os->clock(), new ArgumentTranslator), new ElapsedPeriod(1000), $os->clock(), $os->remote(), @@ -41,15 +40,14 @@ public function testInterface() static fn() => null, ); - $this->assertSame( - $connection, + $this->assertInstanceOf( + SideEffect::class, $connection ->send( static fn($protocol) => $protocol->channel()->open(new Channel(1)), ) - ->connection() ->match( - static fn($connection) => $connection, + static fn($sideEffect) => $sideEffect, static fn() => null, ), ); @@ -72,7 +70,7 @@ public function testClose() $connection = Connection::open( Transport::tcp(), Url::of('//guest:guest@localhost:5672/'), - $protocol = new Protocol($os->clock(), $this->createMock(ArgumentTranslator::class)), + $protocol = new Protocol($os->clock(), new ArgumentTranslator), new ElapsedPeriod(1000), $os->clock(), $os->remote(), @@ -94,7 +92,7 @@ public function testReturnFailureWhenReceivedFrameIsNotTheExpectedOne() $connection = Connection::open( Transport::tcp(), Url::of('//guest:guest@localhost:5672/'), - new Protocol($os->clock(), $this->createMock(ArgumentTranslator::class)), + new Protocol($os->clock(), new ArgumentTranslator), new ElapsedPeriod(1000), $os->clock(), $os->remote(), @@ -107,9 +105,10 @@ public function testReturnFailureWhenReceivedFrameIsNotTheExpectedOne() $this->assertSame( Failure\Kind::unexpectedFrame, $connection - ->send(static fn($protocol) => $protocol->channel()->open(new Channel(2))) - ->wait(Method::connectionOpen) - ->connection() + ->request( + static fn($protocol) => $protocol->channel()->open(new Channel(2)), + Method::connectionOpen, + ) ->match( static fn() => null, static fn($failure) => $failure->kind(), @@ -123,7 +122,7 @@ public function testReturnFailureWhenConnectionClosedByServer() $connection = Connection::open( Transport::tcp(), Url::of('//guest:guest@localhost:5672/'), - $protocol = new Protocol($os->clock(), $this->createMock(ArgumentTranslator::class)), + $protocol = new Protocol($os->clock(), new ArgumentTranslator), new ElapsedPeriod(1000), $os->clock(), $os->remote(), @@ -133,14 +132,13 @@ public function testReturnFailureWhenConnectionClosedByServer() static fn() => null, ); - $connection = $connection->send(static fn() => Sequence::of(Frame::method( + $_ = $connection->send(static fn() => Sequence::of(Frame::method( new Channel(0), Method::of(20, 10), //missing arguments ))) - ->connection() ->match( - static fn($connection) => $connection, + static fn() => null, static fn() => null, ); $this->assertSame( diff --git a/tests/Transport/Frame/Value/BitsTest.php b/tests/Transport/Frame/Value/BitsTest.php index b9ca632..aa20903 100644 --- a/tests/Transport/Frame/Value/BitsTest.php +++ b/tests/Transport/Frame/Value/BitsTest.php @@ -7,8 +7,15 @@ Value\Bits, Value, }; -use Innmind\Stream\Readable\Stream; -use Innmind\Immutable\Sequence; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\{ + Sequence, + Str, +}; use PHPUnit\Framework\TestCase; class BitsTest extends TestCase @@ -34,10 +41,16 @@ public function testStringCast($bits, $expected) */ public function testFromStream($expected, $string) { - $value = Bits::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(Bits::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(Bits::class, $value); $this->assertSame($expected, $value->original()->toList()); diff --git a/tests/Transport/Frame/Value/DecimalTest.php b/tests/Transport/Frame/Value/DecimalTest.php index 6995eb5..ccb3b5d 100644 --- a/tests/Transport/Frame/Value/DecimalTest.php +++ b/tests/Transport/Frame/Value/DecimalTest.php @@ -8,7 +8,12 @@ Transport\Frame\Value, }; use Innmind\Math\Exception\OutOfDefinitionSet; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class DecimalTest extends TestCase @@ -36,10 +41,16 @@ public function testStringCast($number, $scale, $expected) */ public function testFromStream($number, $scale, $string) { - $value = Decimal::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(Decimal::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(Decimal::class, $value); $this->assertSame(($number / (10**$scale)), $value->original()); diff --git a/tests/Transport/Frame/Value/LongStringTest.php b/tests/Transport/Frame/Value/LongStringTest.php index 23d4f2b..e06a4b6 100644 --- a/tests/Transport/Frame/Value/LongStringTest.php +++ b/tests/Transport/Frame/Value/LongStringTest.php @@ -7,7 +7,11 @@ Value\LongString, Value, }; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; @@ -33,10 +37,16 @@ public function testStringCast($string, $expected) */ public function testFromStream($expected, $string) { - $value = LongString::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(LongString::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(LongString::class, $value); $this->assertInstanceOf(Str::class, $value->original()); diff --git a/tests/Transport/Frame/Value/SequenceTest.php b/tests/Transport/Frame/Value/SequenceTest.php index 42f06db..592e070 100644 --- a/tests/Transport/Frame/Value/SequenceTest.php +++ b/tests/Transport/Frame/Value/SequenceTest.php @@ -6,11 +6,14 @@ use Innmind\AMQP\{ Transport\Frame\Value\Sequence, Transport\Frame\Value\LongString, - Transport\Frame\Value\Text, Transport\Frame\Value, }; use Innmind\TimeContinuum\Earth\Clock; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; use Innmind\Immutable\{ Sequence as Seq, Str, @@ -40,10 +43,16 @@ public function testStringCast($expected, $values) */ public function testFromStream($string, $expected) { - $value = Sequence::unpack(new Clock, Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(Sequence::frame(new Clock)) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(Sequence::class, $value); $this->assertCount(\count($expected), $value->original()); diff --git a/tests/Transport/Frame/Value/ShortStringTest.php b/tests/Transport/Frame/Value/ShortStringTest.php index 5ddc402..6ed972d 100644 --- a/tests/Transport/Frame/Value/ShortStringTest.php +++ b/tests/Transport/Frame/Value/ShortStringTest.php @@ -8,7 +8,11 @@ Transport\Frame\Value, }; use Innmind\Math\Exception\OutOfDefinitionSet; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; @@ -34,10 +38,16 @@ public function testStringCast($string, $expected) */ public function testFromStream($expected, $string) { - $value = ShortString::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(ShortString::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(ShortString::class, $value); $this->assertSame($expected, $value->original()->toString()); diff --git a/tests/Transport/Frame/Value/SignedLongIntegerTest.php b/tests/Transport/Frame/Value/SignedLongIntegerTest.php index 9bc1575..88298cf 100644 --- a/tests/Transport/Frame/Value/SignedLongIntegerTest.php +++ b/tests/Transport/Frame/Value/SignedLongIntegerTest.php @@ -8,7 +8,12 @@ Transport\Frame\Value, }; use Innmind\Math\Exception\OutOfDefinitionSet; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class SignedLongIntegerTest extends TestCase @@ -36,10 +41,16 @@ public function testStringCast($int, $expected) */ public function testFromStream($expected, $string) { - $value = SignedLongInteger::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(SignedLongInteger::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(SignedLongInteger::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/SignedLongLongIntegerTest.php b/tests/Transport/Frame/Value/SignedLongLongIntegerTest.php index 8ded10a..bd8fcf3 100644 --- a/tests/Transport/Frame/Value/SignedLongLongIntegerTest.php +++ b/tests/Transport/Frame/Value/SignedLongLongIntegerTest.php @@ -7,7 +7,12 @@ Value\SignedLongLongInteger, Value, }; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class SignedLongLongIntegerTest extends TestCase @@ -35,10 +40,16 @@ public function testStringCast($int, $expected) */ public function testFromStream($expected, $string) { - $value = SignedLongLongInteger::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(SignedLongLongInteger::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(SignedLongLongInteger::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/SignedOctetTest.php b/tests/Transport/Frame/Value/SignedOctetTest.php index cfa0692..f2adb5e 100644 --- a/tests/Transport/Frame/Value/SignedOctetTest.php +++ b/tests/Transport/Frame/Value/SignedOctetTest.php @@ -8,7 +8,12 @@ Transport\Frame\Value, }; use Innmind\Math\Exception\OutOfDefinitionSet; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class SignedOctetTest extends TestCase @@ -33,10 +38,16 @@ public function testStringCast($expected, $octet) */ public function testFromStream($string, $expected) { - $value = SignedOctet::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(SignedOctet::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(SignedOctet::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/SignedShortIntegerTest.php b/tests/Transport/Frame/Value/SignedShortIntegerTest.php index eff9c74..2c1c21b 100644 --- a/tests/Transport/Frame/Value/SignedShortIntegerTest.php +++ b/tests/Transport/Frame/Value/SignedShortIntegerTest.php @@ -8,7 +8,12 @@ Transport\Frame\Value, }; use Innmind\Math\Exception\OutOfDefinitionSet; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class SignedShortIntegerTest extends TestCase @@ -36,10 +41,16 @@ public function testStringCast($int, $expected) */ public function testFromStream($expected, $string) { - $value = SignedShortInteger::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(SignedShortInteger::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(SignedShortInteger::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/TableTest.php b/tests/Transport/Frame/Value/TableTest.php index 1b76f4c..b3df39a 100644 --- a/tests/Transport/Frame/Value/TableTest.php +++ b/tests/Transport/Frame/Value/TableTest.php @@ -7,11 +7,14 @@ Transport\Frame\Value\Table, Transport\Frame\Value\SignedOctet, Transport\Frame\Value\LongString, - Transport\Frame\Value\Text, Transport\Frame\Value, }; use Innmind\TimeContinuum\Earth\Clock; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; use Innmind\Immutable\{ Map, Str, @@ -43,10 +46,16 @@ public function testStringCast($expected, $map) */ public function testFromStream($string, $expected) { - $value = Table::unpack(new Clock, Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(Table::frame(new Clock)) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(Table::class, $value); $this->assertCount($expected->size(), $value->original()); diff --git a/tests/Transport/Frame/Value/TimestampTest.php b/tests/Transport/Frame/Value/TimestampTest.php index ddec29f..f5bc23e 100644 --- a/tests/Transport/Frame/Value/TimestampTest.php +++ b/tests/Transport/Frame/Value/TimestampTest.php @@ -13,7 +13,12 @@ Earth\Clock, PointInTime as PointInTimeInterface, }; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class TimestampTest extends TestCase @@ -35,10 +40,16 @@ public function testStringCast() public function testFromStream() { - $value = Timestamp::unpack(new Clock, Stream::ofContent(\pack('J', $time = \time())))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent(\pack('J', $time = \time()))) + ->toEncoding(Str\Encoding::ascii) + ->frames(Timestamp::frame(new Clock)) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(Timestamp::class, $value); $this->assertInstanceOf(PointInTimeInterface::class, $value->original()); diff --git a/tests/Transport/Frame/Value/UnsignedLongIntegerTest.php b/tests/Transport/Frame/Value/UnsignedLongIntegerTest.php index 361cf9c..ae59ba6 100644 --- a/tests/Transport/Frame/Value/UnsignedLongIntegerTest.php +++ b/tests/Transport/Frame/Value/UnsignedLongIntegerTest.php @@ -7,11 +7,13 @@ Transport\Frame\Value\UnsignedLongInteger, Transport\Frame\Value, }; -use Innmind\Math\{ - Algebra\Integer, - Exception\OutOfDefinitionSet, +use Innmind\Math\Exception\OutOfDefinitionSet; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, }; -use Innmind\Stream\Readable\Stream; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class UnsignedLongIntegerTest extends TestCase @@ -56,10 +58,16 @@ public function testStringCast($int, $expected) */ public function testFromStream($expected, $string) { - $value = UnsignedLongInteger::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(UnsignedLongInteger::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(UnsignedLongInteger::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/UnsignedLongLongIntegerTest.php b/tests/Transport/Frame/Value/UnsignedLongLongIntegerTest.php index 0bd62f0..cf7f1a5 100644 --- a/tests/Transport/Frame/Value/UnsignedLongLongIntegerTest.php +++ b/tests/Transport/Frame/Value/UnsignedLongLongIntegerTest.php @@ -7,11 +7,13 @@ Transport\Frame\Value\UnsignedLongLongInteger, Transport\Frame\Value, }; -use Innmind\Math\{ - Algebra\Integer, - Exception\OutOfDefinitionSet, +use Innmind\Math\Exception\OutOfDefinitionSet; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, }; -use Innmind\Stream\Readable\Stream; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class UnsignedLongLongIntegerTest extends TestCase @@ -47,10 +49,16 @@ public function testStringCast($int, $expected) */ public function testFromStream($expected, $string) { - $value = UnsignedLongLongInteger::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(UnsignedLongLongInteger::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(UnsignedLongLongInteger::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/UnsignedOctetTest.php b/tests/Transport/Frame/Value/UnsignedOctetTest.php index eef17d6..2478383 100644 --- a/tests/Transport/Frame/Value/UnsignedOctetTest.php +++ b/tests/Transport/Frame/Value/UnsignedOctetTest.php @@ -7,11 +7,13 @@ Transport\Frame\Value\UnsignedOctet, Transport\Frame\Value, }; -use Innmind\Math\{ - Algebra\Integer, - Exception\OutOfDefinitionSet, +use Innmind\Math\Exception\OutOfDefinitionSet; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, }; -use Innmind\Stream\Readable\Stream; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class UnsignedOctetTest extends TestCase @@ -39,10 +41,16 @@ public function testStringCast($expected, $octet) */ public function testFromStream($string, $expected) { - $value = UnsignedOctet::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(UnsignedOctet::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(UnsignedOctet::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/UnsignedShortIntegerTest.php b/tests/Transport/Frame/Value/UnsignedShortIntegerTest.php index 47624f8..05ad8d4 100644 --- a/tests/Transport/Frame/Value/UnsignedShortIntegerTest.php +++ b/tests/Transport/Frame/Value/UnsignedShortIntegerTest.php @@ -8,7 +8,12 @@ Transport\Frame\Value, }; use Innmind\Math\Exception\OutOfDefinitionSet; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; +use Innmind\Immutable\Str; use PHPUnit\Framework\TestCase; class UnsignedShortIntegerTest extends TestCase @@ -36,10 +41,16 @@ public function testStringCast($int, $expected) */ public function testFromStream($expected, $string) { - $value = UnsignedShortInteger::unpack(Stream::ofContent($string))->match( - static fn($value) => $value, - static fn() => null, - ); + $value = IO::of(Select::waitForever(...)) + ->readable() + ->wrap(Stream::ofContent($string)) + ->toEncoding(Str\Encoding::ascii) + ->frames(UnsignedShortInteger::frame()) + ->one() + ->match( + static fn($value) => $value->unwrap(), + static fn() => null, + ); $this->assertInstanceOf(UnsignedShortInteger::class, $value); $this->assertSame($expected, $value->original()); diff --git a/tests/Transport/Frame/Value/VoidValueTest.php b/tests/Transport/Frame/Value/VoidValueTest.php index 555e99e..e7cb31f 100644 --- a/tests/Transport/Frame/Value/VoidValueTest.php +++ b/tests/Transport/Frame/Value/VoidValueTest.php @@ -7,7 +7,6 @@ Value\VoidValue, Value, }; -use Innmind\Stream\Readable\Stream; use PHPUnit\Framework\TestCase; class VoidValueTest extends TestCase @@ -15,10 +14,6 @@ class VoidValueTest extends TestCase public function testInterface() { $this->assertInstanceOf(Value::class, new VoidValue); - $this->assertInstanceOf(VoidValue::class, VoidValue::unpack(Stream::ofContent(''))->match( - static fn($value) => $value, - static fn() => null, - )); $this->assertSame('', (new VoidValue)->pack()->toString()); } } diff --git a/tests/Transport/Frame/Visitor/ChunkArgumentsTest.php b/tests/Transport/Frame/Visitor/ChunkArgumentsTest.php deleted file mode 100644 index 7c5d6b3..0000000 --- a/tests/Transport/Frame/Visitor/ChunkArgumentsTest.php +++ /dev/null @@ -1,54 +0,0 @@ -pack()->toString().LongString::literal('foo')->pack()->toString(); - - $stream = $visit(Stream::ofContent($arguments))->match( - static fn($arguments) => $arguments, - static fn() => null, - ); - - $this->assertInstanceOf(Sequence::class, $stream); - $this->assertCount(2, $stream); - $this->assertInstanceOf(Bits::class, $stream->get(0)->match( - static fn($value) => $value, - static fn() => null, - )); - $this->assertInstanceOf(LongString::class, $stream->get(1)->match( - static fn($value) => $value, - static fn() => null, - )); - $this->assertSame([true], $stream->get(0)->match( - static fn($value) => $value->original()->toList(), - static fn() => null, - )); - $this->assertSame('foo', $stream->get(1)->match( - static fn($value) => $value->original()->toString(), - static fn() => null, - )); - } -} diff --git a/tests/Transport/FrameTest.php b/tests/Transport/FrameTest.php index 2b21b0c..fd8f051 100644 --- a/tests/Transport/FrameTest.php +++ b/tests/Transport/FrameTest.php @@ -9,7 +9,6 @@ Frame\Channel, Frame\Method, Frame\MethodClass, - Frame\Value, Frame\Value\Bits, Frame\Value\LongString, }; diff --git a/tests/Transport/Protocol/ArgumentTranslator/DelegateTest.php b/tests/Transport/Protocol/ArgumentTranslator/DelegateTest.php deleted file mode 100644 index 1c9b338..0000000 --- a/tests/Transport/Protocol/ArgumentTranslator/DelegateTest.php +++ /dev/null @@ -1,69 +0,0 @@ -assertInstanceOf(ArgumentTranslator::class, new Delegate); - } - - public function testInvokation() - { - $translate = new Delegate( - $first = $this->createMock(ArgumentTranslator::class), - $second = $this->createMock(ArgumentTranslator::class), - $third = $this->createMock(ArgumentTranslator::class), - ); - $value = 'foo'; - $first - ->expects($this->once()) - ->method('__invoke') - ->with($value) - ->will($this->throwException(new ValueNotTranslatable($value))); - $second - ->expects($this->once()) - ->method('__invoke') - ->with($value) - ->willReturn($expected = $this->createMock(Value::class)); - $third - ->expects($this->never()) - ->method('__invoke'); - - $this->assertSame($expected, $translate($value)); - } - - public function testThrowWhenValueNotTranslatable() - { - $translate = new Delegate( - $inner = $this->createMock(ArgumentTranslator::class), - ); - $value = 'foo'; - $inner - ->expects($this->once()) - ->method('__invoke') - ->with($value) - ->will( - $exception = $this->throwException(new ValueNotTranslatable($value)), - ); - - try { - $translate($value); - $this->fail('it should throw an exception'); - } catch (ValueNotTranslatable $e) { - //verify it's the delegate that throws its own exception and not the inner - $this->assertNotSame($exception, $e); - $this->assertSame($value, $e->value()); - } - } -} diff --git a/tests/Transport/Protocol/ArgumentTranslator/ValueTranslatorTest.php b/tests/Transport/Protocol/ArgumentTranslator/ValueTranslatorTest.php deleted file mode 100644 index 744af45..0000000 --- a/tests/Transport/Protocol/ArgumentTranslator/ValueTranslatorTest.php +++ /dev/null @@ -1,38 +0,0 @@ -assertInstanceOf(ArgumentTranslator::class, new ValueTranslator); - } - - public function testInvokation() - { - $value = $this->createMock(Value::class); - - $this->assertSame($value, (new ValueTranslator)($value)); - } - - public function testThrowWhenValueNotTranslatable() - { - try { - $value = new \stdClass; - (new ValueTranslator)($value); - $this->fail('it should throw an exception'); - } catch (ValueNotTranslatable $e) { - $this->assertSame($value, $e->value()); - } - } -} diff --git a/tests/Transport/Protocol/ArgumentTranslatorTest.php b/tests/Transport/Protocol/ArgumentTranslatorTest.php new file mode 100644 index 0000000..be526c5 --- /dev/null +++ b/tests/Transport/Protocol/ArgumentTranslatorTest.php @@ -0,0 +1,124 @@ +assertInstanceOf(ArgumentTranslator::class, new ArgumentTranslator); + } + + public function testInvokation() + { + $value = $this->createMock(Value::class); + + $this->assertSame($value, (new ArgumentTranslator)($value)); + } + + public function testWideRangeOfValues() + { + $primitive = Set\Either::any( + Set\Integers::any(), + PointInTime::any(), + ); + + $this + ->forAll($primitive) + ->then(function($value) { + $this->assertInstanceOf( + Value::class, + (new ArgumentTranslator)($value), + ); + $this->assertSame( + $value, + (new ArgumentTranslator)($value)->original(), + ); + }); + $this + ->forAll(Set\Unicode::strings()) + ->then(function($value) { + $this->assertInstanceOf( + Value::class, + (new ArgumentTranslator)($value), + ); + $this->assertSame( + $value, + (new ArgumentTranslator)($value)->original()->toString(), + ); + }); + $this + ->forAll(Set\Sequence::of($primitive)->map(static fn($values) => Sequence::of(...$values))) + ->then(function($value) { + $this->assertInstanceOf( + Value::class, + (new ArgumentTranslator)($value), + ); + $this->assertSame( + $value->toList(), + (new ArgumentTranslator)($value) + ->original() + ->map(static fn($value) => $value->original()) + ->toList(), + ); + }); + $this + ->forAll( + Set\Sequence::of( + Set\Strings::madeOf(Set\Chars::alphanumerical()) + ->atMost(255), + )->atMost(20), + Set\Sequence::of($primitive)->atMost(20), + ) + ->then(function($keys, $values) { + $max = \min(\count($keys), \count($values)); + $value = Map::of(); + + for ($i = 0; $i < $max; ++$i) { + $value = ($value)($keys[$i], $values[$i]); + } + + $this->assertInstanceOf( + Value::class, + (new ArgumentTranslator)($value), + ); + $this->assertTrue( + $value->equals( + (new ArgumentTranslator)($value) + ->original() + ->map(static fn($_, $value) => $value->original()), + ), + ); + }); + } + + public function testThrowWhenValueNotTranslatable() + { + try { + $value = new \stdClass; + (new ArgumentTranslator)($value); + $this->fail('it should throw an exception'); + } catch (ValueNotTranslatable $e) { + $this->assertSame($value, $e->value()); + } + } +} diff --git a/tests/Transport/Protocol/BasicTest.php b/tests/Transport/Protocol/BasicTest.php index e128079..26cdbe6 100644 --- a/tests/Transport/Protocol/BasicTest.php +++ b/tests/Transport/Protocol/BasicTest.php @@ -6,7 +6,6 @@ use Innmind\AMQP\{ Transport\Protocol\Basic, Transport\Protocol\ArgumentTranslator, - Transport\Protocol\ArgumentTranslator\ValueTranslator, Transport\Frame, Transport\Frame\Channel, Transport\Frame\Method, @@ -54,13 +53,10 @@ class BasicTest extends TestCase { private $basic; - private $translator; public function setUp(): void { - $this->basic = new Basic( - $this->translator = $this->createMock(ArgumentTranslator::class), - ); + $this->basic = new Basic(new ArgumentTranslator); } public function testAck() @@ -172,23 +168,6 @@ public function testCancel() public function testConsume() { - $firstArgument = UnsignedShortInteger::of(24); - $secondArgument = UnsignedShortInteger::of(42); - $this - ->translator - ->expects($matcher = $this->exactly(2)) - ->method('__invoke') - ->willReturnCallback(function($value) use ($matcher, $firstArgument, $secondArgument) { - match ($matcher->numberOfInvocations()) { - 1 => $this->assertSame(24, $value), - 2 => $this->assertSame(42, $value), - }; - - return match ($matcher->numberOfInvocations()) { - 1 => $firstArgument, - 2 => $secondArgument, - }; - }); $frame = $this->basic->consume( $channel = new Channel(1), Consume::of('queue') @@ -250,16 +229,16 @@ public function testConsume() static fn($value) => $value->original(), static fn() => null, )); - $this->assertSame($firstArgument, $frame->values()->get(4)->match( + $this->assertSame(24, $frame->values()->get(4)->match( static fn($value) => $value->original()->get('foo')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, )); - $this->assertSame($secondArgument, $frame->values()->get(4)->match( + $this->assertSame(42, $frame->values()->get(4)->match( static fn($value) => $value->original()->get('bar')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, @@ -562,7 +541,7 @@ public function testPublishWithChunkedMessage() public function testPublishWithProperties() { - $basic = new Basic(new ValueTranslator); + $basic = new Basic(new ArgumentTranslator); $frames = $basic->publish( $channel = new Channel(1), diff --git a/tests/Transport/Protocol/ExchangeTest.php b/tests/Transport/Protocol/ExchangeTest.php index 8beaa50..02595dd 100644 --- a/tests/Transport/Protocol/ExchangeTest.php +++ b/tests/Transport/Protocol/ExchangeTest.php @@ -22,34 +22,14 @@ class ExchangeTest extends TestCase { private $exchange; - private $translator; public function setUp(): void { - $this->exchange = new Exchange( - $this->translator = $this->createMock(ArgumentTranslator::class), - ); + $this->exchange = new Exchange(new ArgumentTranslator); } public function testDeclare() { - $firstArgument = UnsignedShortInteger::of(24); - $secondArgument = UnsignedShortInteger::of(42); - $this - ->translator - ->expects($matcher = $this->exactly(2)) - ->method('__invoke') - ->willReturnCallback(function($value) use ($matcher, $firstArgument, $secondArgument) { - match ($matcher->numberOfInvocations()) { - 1 => $this->assertSame(24, $value), - 2 => $this->assertSame(42, $value), - }; - - return match ($matcher->numberOfInvocations()) { - 1 => $firstArgument, - 2 => $secondArgument, - }; - }); $frame = $this->exchange->declare( $channel = new Channel(1), Declaration::passive('foo', Type::direct) @@ -111,16 +91,16 @@ public function testDeclare() static fn($value) => $value->original(), static fn() => null, )); - $this->assertSame($firstArgument, $frame->values()->get(4)->match( + $this->assertSame(24, $frame->values()->get(4)->match( static fn($value) => $value->original()->get('foo')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, )); - $this->assertSame($secondArgument, $frame->values()->get(4)->match( + $this->assertSame(42, $frame->values()->get(4)->match( static fn($value) => $value->original()->get('bar')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, diff --git a/tests/Transport/Protocol/QueueTest.php b/tests/Transport/Protocol/QueueTest.php index d6b0ab8..77855cf 100644 --- a/tests/Transport/Protocol/QueueTest.php +++ b/tests/Transport/Protocol/QueueTest.php @@ -10,7 +10,6 @@ Transport\Frame\Channel, Transport\Frame\Method, Transport\Frame\Type, - Transport\Frame\Value, Transport\Frame\Value\UnsignedShortInteger, Transport\Frame\Value\ShortString, Transport\Frame\Value\Bits, @@ -26,34 +25,14 @@ class QueueTest extends TestCase { private $queue; - private $translator; public function setUp(): void { - $this->queue = new Queue( - $this->translator = $this->createMock(ArgumentTranslator::class), - ); + $this->queue = new Queue(new ArgumentTranslator); } public function testDeclare() { - $firstArgument = UnsignedShortInteger::of(24); - $secondArgument = UnsignedShortInteger::of(42); - $this - ->translator - ->expects($matcher = $this->exactly(2)) - ->method('__invoke') - ->willReturnCallback(function($value) use ($matcher, $firstArgument, $secondArgument) { - match ($matcher->numberOfInvocations()) { - 1 => $this->assertSame(24, $value), - 2 => $this->assertSame(42, $value), - }; - - return match ($matcher->numberOfInvocations()) { - 1 => $firstArgument, - 2 => $secondArgument, - }; - }); $frame = $this->queue->declare( $channel = new Channel(1), Declaration::passive('foo') @@ -107,16 +86,16 @@ public function testDeclare() static fn($value) => $value->original(), static fn() => null, )); - $this->assertSame($firstArgument, $frame->values()->get(3)->match( + $this->assertSame(24, $frame->values()->get(3)->match( static fn($value) => $value->original()->get('foo')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, )); - $this->assertSame($secondArgument, $frame->values()->get(3)->match( + $this->assertSame(42, $frame->values()->get(3)->match( static fn($value) => $value->original()->get('bar')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, @@ -333,23 +312,6 @@ public function testDelete() public function testBind() { - $firstArgument = UnsignedShortInteger::of(24); - $secondArgument = UnsignedShortInteger::of(42); - $this - ->translator - ->expects($matcher = $this->exactly(2)) - ->method('__invoke') - ->willReturnCallback(function($value) use ($matcher, $firstArgument, $secondArgument) { - match ($matcher->numberOfInvocations()) { - 1 => $this->assertSame(24, $value), - 2 => $this->assertSame(42, $value), - }; - - return match ($matcher->numberOfInvocations()) { - 1 => $firstArgument, - 2 => $secondArgument, - }; - }); $frame = $this->queue->bind( $channel = new Channel(1), Binding::of('ex', 'q', 'rk') @@ -419,16 +381,16 @@ public function testBind() static fn($value) => $value->original(), static fn() => null, )); - $this->assertSame($firstArgument, $frame->values()->get(5)->match( + $this->assertSame(24, $frame->values()->get(5)->match( static fn($value) => $value->original()->get('foo')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, )); - $this->assertSame($secondArgument, $frame->values()->get(5)->match( + $this->assertSame(42, $frame->values()->get(5)->match( static fn($value) => $value->original()->get('bar')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, @@ -453,23 +415,6 @@ public function testBind() public function testUnbind() { - $firstArgument = UnsignedShortInteger::of(24); - $secondArgument = UnsignedShortInteger::of(42); - $this - ->translator - ->expects($matcher = $this->exactly(2)) - ->method('__invoke') - ->willReturnCallback(function($value) use ($matcher, $firstArgument, $secondArgument) { - match ($matcher->numberOfInvocations()) { - 1 => $this->assertSame(24, $value), - 2 => $this->assertSame(42, $value), - }; - - return match ($matcher->numberOfInvocations()) { - 1 => $firstArgument, - 2 => $secondArgument, - }; - }); $frame = $this->queue->unbind( $channel = new Channel(1), Unbinding::of('ex', 'q', 'rk') @@ -528,16 +473,16 @@ public function testUnbind() static fn($value) => $value->original(), static fn() => null, )); - $this->assertSame($firstArgument, $frame->values()->get(4)->match( + $this->assertSame(24, $frame->values()->get(4)->match( static fn($value) => $value->original()->get('foo')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, )); - $this->assertSame($secondArgument, $frame->values()->get(4)->match( + $this->assertSame(42, $frame->values()->get(4)->match( static fn($value) => $value->original()->get('bar')->match( - static fn($argument) => $argument, + static fn($argument) => $argument->original(), static fn() => null, ), static fn() => null, diff --git a/tests/Transport/Protocol/ReaderTest.php b/tests/Transport/Protocol/ReaderTest.php index 49cc153..379554e 100644 --- a/tests/Transport/Protocol/ReaderTest.php +++ b/tests/Transport/Protocol/ReaderTest.php @@ -4,9 +4,7 @@ namespace Tests\Innmind\AMQP\Transport\Protocol; use Innmind\AMQP\Transport\{ - Protocol\Reader, Frame\Method, - Frame\Value, Frame\Value\ShortString, Frame\Value\UnsignedShortInteger, Frame\Value\UnsignedLongLongInteger, @@ -17,6 +15,7 @@ Frame\Value\LongString }; use Innmind\TimeContinuum\Earth\Clock; +use Innmind\IO\IO; use Innmind\Stream\Readable\Stream; use Innmind\Immutable\{ Str, @@ -32,21 +31,21 @@ class ReaderTest extends TestCase */ public function testInvokation($method, $arguments) { - $read = new Reader(new Clock); - $args = ''; foreach ($arguments as $arg) { $args .= $arg->pack()->toString(); } - $stream = $read( - $method, - Stream::ofContent($args), - )->match( - static fn($values) => $values, - static fn() => null, - ); + $stream = IO::of(static fn() => null) + ->readable() + ->wrap(Stream::ofContent($args)) + ->frames($method->incomingFrame(new Clock)) + ->one() + ->match( + static fn($values) => $values, + static fn() => null, + ); $this->assertInstanceOf(Sequence::class, $stream); $this->assertCount(\count($arguments), $stream); diff --git a/tests/Transport/Protocol/VersionTest.php b/tests/Transport/Protocol/VersionTest.php index a4f066e..cfab55f 100644 --- a/tests/Transport/Protocol/VersionTest.php +++ b/tests/Transport/Protocol/VersionTest.php @@ -3,10 +3,7 @@ namespace Tests\Innmind\AMQP\Transport\Protocol; -use Innmind\AMQP\{ - Transport\Protocol\Version, - Exception\DomainException, -}; +use Innmind\AMQP\Transport\Protocol\Version; use PHPUnit\Framework\TestCase; class VersionTest extends TestCase diff --git a/tests/Transport/ProtocolTest.php b/tests/Transport/ProtocolTest.php index 2232a1d..fc8a4fe 100644 --- a/tests/Transport/ProtocolTest.php +++ b/tests/Transport/ProtocolTest.php @@ -13,9 +13,7 @@ Transport\Protocol\Transaction, Transport\Protocol\Version, Transport\Protocol\ArgumentTranslator, - Transport\Protocol\ArgumentTranslator\ValueTranslator, Transport\Frame\Channel as FrameChannel, - Transport\Frame\Value, Transport\Frame\Value\ShortString, Model\Basic\Publish, Model\Basic\Message, @@ -36,7 +34,11 @@ PointInTime\Now, Clock, }; -use Innmind\Stream\Readable\Stream; +use Innmind\IO\IO; +use Innmind\Stream\{ + Readable\Stream, + Watch\Select, +}; use Innmind\Immutable\{ Str, Map, @@ -48,7 +50,7 @@ class ProtocolTest extends TestCase { public function testInterface() { - $protocol = new Protocol(new Clock, $this->createMock(ArgumentTranslator::class)); + $protocol = new Protocol(new Clock, new ArgumentTranslator); $this->assertInstanceOf(Version::class, $protocol->version()); $this->assertSame("AMQP\x00\x00\x09\x01", $protocol->version()->pack()->toString()); @@ -62,7 +64,7 @@ public function testInterface() public function testReadHeader() { - $protocol = new Protocol(new Clock, new ValueTranslator); + $protocol = new Protocol(new Clock, new ArgumentTranslator); $header = $protocol ->basic() @@ -94,20 +96,26 @@ public function testReadHeader() static fn() => null, ); - $values = $protocol->readHeader( - Stream::ofContent( - Str::of('') - ->join( + $values = IO::of(Select::waitForever(...)) + ->readable() + ->wrap( + Stream::ofContent( + \implode( + '', $header ->values() - ->map(static fn($v) => $v->pack()->toString()), - ) - ->toString(), - ), - )->match( - static fn($values) => $values, - static fn() => null, - ); + ->map(static fn($v) => $v->pack()->toString()) + ->toList(), + ), + ), + ) + ->toEncoding(Str\Encoding::ascii) + ->frames($protocol->headerFrame()) + ->one() + ->match( + static fn($values) => $values, + static fn() => null, + ); $this->assertInstanceOf(Sequence::class, $values); $this->assertCount(15, $values); // body size + flag bits + 13 properties