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)); } }