Added RESP3 Parser strategy (#1294)

* Added RESP3 Parser strategy

* Fixed error messages
This commit is contained in:
Vladyslav Vildanov
2023-05-17 13:13:31 +03:00
committed by GitHub
parent 0a6f9e7b70
commit 55f6980594
8 changed files with 396 additions and 19 deletions
+58 -14
View File
@@ -15,6 +15,7 @@ namespace Predis\Connection;
use InvalidArgumentException;
use Predis\Command\CommandInterface;
use Predis\Protocol\Parser\UnexpectedTypeException;
use Predis\Response\Error;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
/**
@@ -301,6 +302,7 @@ class StreamConnection extends AbstractConnection
}
switch ($parsedData['type']) {
case 'push':
case 'array':
$data = [];
@@ -311,22 +313,38 @@ class StreamConnection extends AbstractConnection
return $data;
case 'bulkString':
$bulkData = '';
$size = $parsedData['value'];
$bytesLeft = ($size += 2);
do {
$chunk = is_resource($socket) ? fread($socket, min($bytesLeft, 4096)) : false;
if ($chunk === false || $chunk === '') {
$this->onConnectionError('Error while reading bytes from the server.');
}
$bulkData .= $chunk;
$bytesLeft = $size - strlen($bulkData);
} while ($bytesLeft > 0);
case 'verbatimString':
$bulkData = $this->readByChunks($socket, $parsedData['value']);
return substr($bulkData, 0, -2);
case 'blobError':
$errorMessage = $this->readByChunks($socket, $parsedData['value']);
return new Error(substr($errorMessage, 0, -2));
case 'map':
$data = [];
for ($i = 0; $i < $parsedData['value']; ++$i) {
$key = $this->read();
$data[$key] = $this->read();
}
return $data;
case 'set':
$data = [];
for ($i = 0; $i < $parsedData['value']; ++$i) {
$element = $this->read();
if (!in_array($element, $data, true)) {
$data[] = $element;
}
}
return $data;
}
return $parsedData;
@@ -352,4 +370,30 @@ class StreamConnection extends AbstractConnection
$this->write($buffer);
}
/**
* Reads given resource split on chunks with given size.
*
* @param $resource
* @param int $chunkSize
* @return string
*/
private function readByChunks($resource, int $chunkSize): string
{
$string = '';
$bytesLeft = ($chunkSize += 2);
do {
$chunk = is_resource($resource) ? fread($resource, min($bytesLeft, 4096)) : false;
if ($chunk === false || $chunk === '') {
$this->onConnectionError('Error while reading bytes from the server.');
}
$string .= $chunk;
$bytesLeft = $chunkSize - strlen($string);
} while ($bytesLeft > 0);
return $string;
}
}
@@ -22,7 +22,7 @@ class Resp2Strategy implements ParserStrategyInterface
/**
* Callbacks to process given RESP type.
*
* @var callable[]
* @var string[]
*/
protected $typeCallbacks = [
'+' => 'parseSimpleString',
@@ -14,4 +14,145 @@ namespace Predis\Protocol\Parser\Strategy;
class Resp3Strategy extends Resp2Strategy
{
/**
* @var string[]
*/
protected $resp3TypeCallbacks = [
'_' => 'parseNull',
',' => 'parseDouble',
'#' => 'parseBoolean',
'!' => 'parseBlobError',
'=' => 'parseVerbatimString',
'(' => 'parseBigNumber',
'%' => 'parseMap',
'~' => 'parseSet',
'>' => 'parsePush',
];
public function __construct()
{
$this->typeCallbacks += $this->resp3TypeCallbacks;
}
/**
* Parse null RESP3 type.
*
* @return null
*/
protected function parseNull(string $string)
{
return null;
}
/**
* Parse double RESP3 type.
*
* @param string $string
* @return float
*/
protected function parseDouble(string $string): float
{
if ($string === 'inf' || $string === '-inf') {
return INF;
}
return (float) $string;
}
/**
* Parse boolean RESP3 type.
*
* @param string $string
* @return bool
*/
protected function parseBoolean(string $string): bool
{
return $string === 't';
}
/**
* Parse blob error RESP3 type.
*
* @param string $string
* @return array
*/
protected function parseBlobError(string $string): array
{
return [
'type' => 'blobError',
'value' => (int) $string,
];
}
/**
* Parse verbatim string RESP3 type.
*
* @param string $string
* @return array
*/
protected function parseVerbatimString(string $string): array
{
return [
'type' => 'verbatimString',
'value' => (int) $string,
];
}
/**
* Parse big number RESP3 type.
* Depends on PHP environment returns float on numbers that reaches max integer limit.
*
* @param string $string
* @return int|float
*/
protected function parseBigNumber(string $string)
{
if (bccomp($string, PHP_INT_MAX) === 1) {
return (float) $string;
}
return $this->parseInteger($string);
}
/**
* Parse map RESP3 type.
*
* @param string $string
* @return array
*/
protected function parseMap(string $string): array
{
return [
'type' => 'map',
'value' => (int) $string,
];
}
/**
* Parse set RESP3 type.
*
* @param string $string
* @return array
*/
protected function parseSet(string $string): array
{
return [
'type' => 'set',
'value' => (int) $string,
];
}
/**
* Parse push RESP3 type.
*
* @param string $string
* @return array
*/
protected function parsePush(string $string): array
{
return [
'type' => 'push',
'value' => (int) $string,
];
}
}
@@ -93,7 +93,7 @@ class FTINFO_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown Index name');
$this->expectExceptionMessage('Unknown index name');
$redis->ftinfo('index');
}
@@ -143,7 +143,7 @@ class TSDECRBY_Test extends PredisCommandTestCase
);
$this->expectException(ServerException::class);
$this->expectExceptionMessage('TSDB: for incrby/decrby, timestamp should be newer than the');
$this->expectExceptionMessage('TSDB: timestamp must be equal to or higher than the maximum existing timestamp');
$redis->tsdecrby('temperature:2:32', 27, (new DecrByArguments())->timestamp(123123123122));
}
@@ -143,7 +143,7 @@ class TSINCRBY_Test extends PredisCommandTestCase
);
$this->expectException(ServerException::class);
$this->expectExceptionMessage('TSDB: for incrby/decrby, timestamp should be newer than the');
$this->expectExceptionMessage('TSDB: timestamp must be equal to or higher than the maximum existing timestamp');
$redis->tsincrby('temperature:2:32', 27, (new IncrByArguments())->timestamp(123123123122));
}
@@ -97,7 +97,7 @@ class TSMADD_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage("ERR wrong number of arguments for 'TS.MADD' command");
$this->expectExceptionMessage("ERR wrong number of arguments for 'ts.madd' command");
$redis->tsmadd('temperature:2:32', 123123123123, 27, 'temperature:2:33', 123123123124);
}
@@ -0,0 +1,192 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Protocol\Parser\Strategy;
use PredisTestCase;
class Resp3StrategyTest extends PredisTestCase
{
/**
* @var ParserStrategyInterface
*/
protected $strategy;
protected function setUp(): void
{
parent::setUp();
$this->strategy = new Resp3Strategy();
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsNullOnNullType(): void
{
$data = "_\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertNull($actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsFloatOnDoubleType(): void
{
$data = ",1.23\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(1.23, $actualResponse);
}
/**
* @dataProvider infinityProvider
* @group disconnected
* @param string $data
* @return void
*/
public function testParseDataReturnsFloatInfinityOnInfinityOrNegativeInfinity(string $data): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertInfinite($actualResponse);
}
/**
* @dataProvider booleanProvider
* @group disconnected
* @param string $data
* @param bool $expectedValue
* @return void
*/
public function testParseDataReturnsBooleanOnBooleanType(string $data, bool $expectedValue): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertSame($expectedValue, $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnBlobErrorType(): void
{
$data = "!21\r\nSYNTAX invalid syntax\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'blobError', 'value' => 21], $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnVerbatimStringType(): void
{
$data = "=15\r\ntxt:Some string\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'verbatimString', 'value' => 15], $actualResponse);
}
/**
* @dataProvider bigNumberProvider
* @group disconnected
* @param string $data
* @param int|float $expectedValue
* @return void
*/
public function testParseDataReturnsIntegerOrFloatOnBigNumberType(string $data, $expectedValue): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertSame($expectedValue, $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnMapType(): void
{
$data = "%2\r\n+first\r\n:1\r\n+second\r\n:2";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'map', 'value' => 2], $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnSetType(): void
{
$data = "~4\r\n+first\r\n:1\r\n+second\r\n:2";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'set', 'value' => 4], $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnPushType(): void
{
$data = ">4\r\n+pubsub\r\n+message\r\n+somechannel\r\n+this is the message";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'push', 'value' => 4], $actualResponse);
}
public function infinityProvider(): array
{
return [
'positive infinity' => [",inf\r\n"],
'negative infinity' => [",-inf\r\n"],
];
}
public function booleanProvider(): array
{
return [
'true' => ["#t\r\n", true],
'false' => ["#f\r\n", false],
];
}
public function bigNumberProvider(): array
{
return [
'greater than integer limit' => [
"(3492890328409238509324850943850943825024385\r\n",
3492890328409238509324850943850943825024385,
],
'lower than integer limit' => [
"(34928903\r\n",
34928903,
],
];
}
}