diff --git a/.gitignore b/.gitignore
index c89b8c5..45d92a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,5 @@ build
clover.xml
.env
builds
+
+.phpunit.result.cache
diff --git a/.styleci.yml b/.styleci.yml
index 9b7d3ec..5975259 100644
--- a/.styleci.yml
+++ b/.styleci.yml
@@ -74,3 +74,4 @@ enabled:
finder:
exclude:
- "tests/app/GRPC/Generator"
+ - "tests/generated"
diff --git a/composer.json b/composer.json
index 34cb886..09b1e74 100644
--- a/composer.json
+++ b/composer.json
@@ -25,7 +25,7 @@
"grpc/grpc": "^1.42",
"roadrunner-php/centrifugo": "^2.0",
"spiral/roadrunner-http": "^3.0",
- "spiral/roadrunner-grpc": "^3.0",
+ "spiral/roadrunner-grpc": "^3.2",
"spiral/roadrunner-jobs": "^4.0",
"spiral/roadrunner-kv": "^4.0",
"spiral/roadrunner-tcp": "^3.0",
@@ -48,6 +48,8 @@
},
"autoload-dev": {
"psr-4": {
+ "GPBMetadata\\": "tests/generated/GPBMetadata",
+ "Service\\": "tests/generated/Service",
"Spiral\\App\\": "tests/app",
"Spiral\\Tests\\": "tests/src"
}
diff --git a/src/GRPC/Interceptor/Invoker.php b/src/GRPC/Interceptor/Invoker.php
index 84d11bd..32d915a 100644
--- a/src/GRPC/Interceptor/Invoker.php
+++ b/src/GRPC/Interceptor/Invoker.php
@@ -4,11 +4,14 @@
namespace Spiral\RoadRunnerBridge\GRPC\Interceptor;
+use Google\Protobuf\Internal\Message;
use Spiral\Core\CoreInterface;
use Spiral\RoadRunner\GRPC\ContextInterface;
+use Spiral\RoadRunner\GRPC\Exception\InvokeException;
use Spiral\RoadRunner\GRPC\InvokerInterface;
use Spiral\RoadRunner\GRPC\Method;
use Spiral\RoadRunner\GRPC\ServiceInterface;
+use Spiral\RoadRunner\GRPC\StatusCode;
/**
* @internal
@@ -16,7 +19,7 @@
final class Invoker implements InvokerInterface
{
public function __construct(
- private readonly CoreInterface $core
+ private readonly CoreInterface $core,
) {
}
@@ -27,6 +30,30 @@ public function invoke(ServiceInterface $service, Method $method, ContextInterfa
'method' => $method,
'ctx' => $ctx,
'input' => $input,
+ 'message' => $this->makeInput($method, $input),
]);
}
+
+ /**
+ * Converts the input from the GRPC service method to the Message object.
+ *
+ * @throws InvokeException
+ */
+ private function makeInput(Method $method, ?string $body): Message
+ {
+ try {
+ $class = $method->inputType;
+
+ /** @psalm-suppress UnsafeInstantiation */
+ $in = new $class();
+
+ if ($body !== null) {
+ $in->mergeFromString($body);
+ }
+
+ return $in;
+ } catch (\Throwable $e) {
+ throw InvokeException::create($e->getMessage(), StatusCode::INTERNAL, $e);
+ }
+ }
}
diff --git a/src/GRPC/Interceptor/InvokerCore.php b/src/GRPC/Interceptor/InvokerCore.php
index 4b16f3d..307c31a 100644
--- a/src/GRPC/Interceptor/InvokerCore.php
+++ b/src/GRPC/Interceptor/InvokerCore.php
@@ -4,6 +4,7 @@
namespace Spiral\RoadRunnerBridge\GRPC\Interceptor;
+use Google\Protobuf\Internal\Message;
use Spiral\Core\CoreInterface;
use Spiral\RoadRunner\GRPC\ContextInterface;
use Spiral\RoadRunner\GRPC\InvokerInterface;
@@ -13,7 +14,7 @@
final class InvokerCore implements CoreInterface
{
public function __construct(
- private readonly InvokerInterface $invoker
+ private readonly InvokerInterface $invoker,
) {
}
@@ -22,13 +23,21 @@ public function callAction(string $controller, string $action, array $parameters
\assert($parameters['service'] instanceof ServiceInterface);
\assert($parameters['method'] instanceof Method);
\assert($parameters['ctx'] instanceof ContextInterface);
- \assert(\is_string($parameters['input']) || null === $parameters['input']);
+ \assert(
+ \is_string($parameters['input'])
+ || null === $parameters['input'],
+ );
+
+ $input = (isset($parameters['message']) && $parameters['message'] instanceof Message)
+ ? $parameters['message']
+ : $parameters['input'];
+ /** @psalm-suppress PossiblyInvalidArgument */
return $this->invoker->invoke(
$parameters['service'],
$parameters['method'],
$parameters['ctx'],
- $parameters['input']
+ $input,
);
}
}
diff --git a/tests/app/GRPC/Message.php b/tests/app/GRPC/Message.php
deleted file mode 100644
index 7bc659b..0000000
--- a/tests/app/GRPC/Message.php
+++ /dev/null
@@ -1,9 +0,0 @@
-internalAddGeneratedFile(hex2bin(
+ "0a6e0a0d736572766963652e70726f746f12077365727669636522160a07" .
+ "4d657373616765120b0a036d736718012001280932340a044563686f122c" .
+ "0a0450696e6712102e736572766963652e4d6573736167651a102e736572" .
+ "766963652e4d6573736167652200620670726f746f33"
+ ));
+
+ static::$is_initialized = true;
+ }
+}
+
diff --git a/tests/generated/Service/Message.php b/tests/generated/Service/Message.php
new file mode 100644
index 0000000..c1ea30d
--- /dev/null
+++ b/tests/generated/Service/Message.php
@@ -0,0 +1,56 @@
+service.Message
+ */
+class Message extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string msg = 1;
+ */
+ private $msg = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $msg
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Service::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string msg = 1;
+ * @return string
+ */
+ public function getMsg()
+ {
+ return $this->msg;
+ }
+
+ /**
+ * Generated from protobuf field string msg = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setMsg($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->msg = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/tests/generated/Service/PingInterface.php b/tests/generated/Service/PingInterface.php
new file mode 100644
index 0000000..ca828da
--- /dev/null
+++ b/tests/generated/Service/PingInterface.php
@@ -0,0 +1,22 @@
+setMsg('PONG');
+ }
+}
diff --git a/tests/src/GRPC/DispatcherTest.php b/tests/src/GRPC/DispatcherTest.php
index be563cc..fcf4daa 100644
--- a/tests/src/GRPC/DispatcherTest.php
+++ b/tests/src/GRPC/DispatcherTest.php
@@ -32,7 +32,7 @@ public function testCanServe(): void
$this->assertDispatcherCanBeServed(Dispatcher::class);
}
- public function testServe()
+ public function testServe(): void
{
$worker = $this->mockContainer(WorkerInterface::class, Worker::class);
$this->getContainer()->bind(RoadRunnerMode::class, RoadRunnerMode::Grpc);
@@ -48,7 +48,8 @@ public function testServe()
);
$worker->shouldReceive('respond')->once()->withArgs(function (Payload $payload) {
- return $payload->body === (new Message())->setMsg('PONG')->serializeToString();
+ $this->assertSame($payload->body, (new Message())->setMsg('PONG')->serializeToString());
+ return true;
});
$worker->shouldReceive('waitPayload')->once()->with()->andReturnNull();
diff --git a/tests/src/GRPC/Interceptor/InvokerCoreTest.php b/tests/src/GRPC/Interceptor/InvokerCoreTest.php
index 5fe7e39..ad4ff0e 100644
--- a/tests/src/GRPC/Interceptor/InvokerCoreTest.php
+++ b/tests/src/GRPC/Interceptor/InvokerCoreTest.php
@@ -4,7 +4,7 @@
namespace Spiral\Tests\GRPC\Interceptor;
-use Spiral\App\GRPC\PingService;
+use Service\PingService;
use Spiral\RoadRunner\GRPC\ContextInterface;
use Spiral\RoadRunner\GRPC\InvokerInterface;
use Spiral\RoadRunnerBridge\GRPC\Interceptor\InvokerCore;
diff --git a/tests/src/GRPC/Interceptor/InvokerTest.php b/tests/src/GRPC/Interceptor/InvokerTest.php
index 57bd2d7..8aa2cca 100644
--- a/tests/src/GRPC/Interceptor/InvokerTest.php
+++ b/tests/src/GRPC/Interceptor/InvokerTest.php
@@ -4,14 +4,16 @@
namespace Spiral\Tests\GRPC\Interceptor;
-use Spiral\App\GRPC\PingService;
+use Mockery as m;
+use Service\PingService;
use Spiral\Core\CoreInterface;
+use Service\Message;
use Spiral\RoadRunner\GRPC\ContextInterface;
+use Spiral\RoadRunner\GRPC\Exception\InvokeException;
use Spiral\RoadRunner\GRPC\Method;
use Spiral\RoadRunner\GRPC\ServiceInterface;
use Spiral\RoadRunnerBridge\GRPC\Interceptor\Invoker;
use Spiral\Tests\TestCase;
-use Mockery as m;
final class InvokerTest extends TestCase
{
@@ -22,16 +24,40 @@ public function testInvoke(): void
$service = m::mock(ServiceInterface::class);
$method = Method::parse(new \ReflectionMethod(PingService::class, 'Ping'));
+ $input = (new Message(['msg' => 'hello']))->serializeToString();
+ $output = (new Message(['msg' => 'world']))->serializeToString();
+
+ $ctx = m::mock(ContextInterface::class);
$core
->shouldReceive('callAction')
->once()
- ->with($service::class, 'Ping', [
- 'service' => $service,
- 'method' => $method,
- 'ctx' => $ctx = m::mock(ContextInterface::class),
- 'input' => $input = 'test',
- ])->andReturn('hello');
-
- $this->assertSame('hello', $invoker->invoke($service, $method, $ctx, $input));
+ ->withArgs(function (string $class, string $method, array $params) use ($service, $input) {
+ $this->assertSame($class, $service::class);
+ $this->assertSame('Ping', $method);
+ $this->assertInstanceOf(ContextInterface::class, $params['ctx']);
+ $this->assertSame($input, $params['input']);
+ $this->assertInstanceOf(Message::class, $params['message']);
+ $this->assertSame('hello', $params['message']->getMsg());
+
+ return true;
+ })->andReturn($output);
+
+ $this->assertSame($output, $invoker->invoke($service, $method, $ctx, $input));
+ }
+
+ public function testInvokeWithBrokenText(): void
+ {
+ $this->expectException(InvokeException::class);
+
+ $invoker = new Invoker(m::mock(CoreInterface::class));
+
+ $service = m::mock(ServiceInterface::class);
+ $method = Method::parse(new \ReflectionMethod(PingService::class, 'Ping'));
+
+ $input = 'input';
+ $output = 'output';
+
+ $ctx = m::mock(ContextInterface::class);
+ $this->assertSame($output, $invoker->invoke($service, $method, $ctx, $input));
}
}