diff --git a/.gitignore b/.gitignore index 1d5c172..178ff43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,55 +1,4 @@ -/*.pnproj -/.idea -.htaccess -atlassian-ide-plugin.xml - -### Symfony template -# Cache and logs (Symfony2) -/app/cache/* -/app/logs/* -!app/cache/.gitkeep -!app/logs/.gitkeep - -# Cache and logs (Symfony3) -/var/* -!/var/cache -/var/cache/* -!var/cache/.gitkeep -!/var/logs -/var/logs/* -!var/logs/.gitkeep -!/var/sessions -/var/sessions/* -!var/sessions/.gitkeep -var/SymfonyRequirements.php - -# Parameters -/app/config/parameters.yml -/app/config/parameters.local.yml -/app/config/parameters.ini - -# Managed by Composer -/app/bootstrap.php.cache -/var/bootstrap.php.cache -/bin/* -!bin/console -bin/symfony_requirements /vendor/ - -# Assets and user uploads -/web/bundles/ -/web/uploads/ - -# PHPUnit -/app/phpunit.xml - -# Build data /build/ /target/ - -# Any PHARs -*.phar - -Thumbs.db - composer.lock diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 7b7e67b..d0b662d 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -2,7 +2,7 @@ build: tests: override: - - command: phpunit --coverage-clover=build/clover.xml + command: JMS_BUNDLE=1 DOCTRINE_BUNDLE=1 phpunit --coverage-clover=build/clover.xml coverage: file: build/clover.xml format: php-clover diff --git a/.travis.yml b/.travis.yml index 75401ab..a97ff9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,53 @@ language: php php: - - 5.5 - - 5.6 - - 7 - nightly - - hhvm + - 7.1 + - 7 + - 5.6 + - 5.5 + +## Run on container environment +sudo: false + +## Cache composer bits +cache: + directories: + - $HOME/.composer/cache env: - - PACKAGES='symfony/symfony=2.7.*' - - PACKAGES='symfony/symfony=2.8.*' - - PACKAGES='symfony/symfony=3.1.*' + - PACKAGES='symfony/symfony=~3.4' STABILITY=dev + - PACKAGES='symfony/symfony=3.3.*' - PACKAGES='symfony/symfony=3.2.*' - - PACKAGES='symfony/symfony=~3.3@dev' + - PACKAGES='symfony/symfony=2.8.*' + - PACKAGES='symfony/symfony=2.7.*' matrix: + fast_finish: true include: - - php: 5.5 - env: PACKAGES='symfony/symfony=2.7.*' deps='low' + - php: 7.1 + env: PACKAGES='symfony/symfony=~4.0' STABILITY=dev + - php: nightly + env: PACKAGES='symfony/symfony=~4.0' STABILITY=dev allow_failures: - - php: hhvm - php: nightly + - env: PACKAGES='symfony/symfony=~4.0' STABILITY=dev before_install: - travis_retry composer self-update install: - composer require --no-update ${PACKAGES} - - if [ "$deps" = "no"] || [ -z "$deps" ]; then composer --prefer-source install; fi; - - if [ "$deps" = "low" ]; then composer --prefer-source --prefer-lowest --prefer-stable update; composer --prefer-source install; fi; + - if [ "$STABILITY" = "dev" ]; then composer config minimum-stability dev; fi; + - composer --prefer-source install script: + - rm -rf build - mkdir -p build - - vendor/bin/phpunit --colors -c phpunit.xml + - vendor/bin/phpunit --colors -c phpunit.xml --testsuite unit + - vendor/bin/phpunit --colors -c phpunit.xml --testsuite integration + - JMS_BUNDLE=1 vendor/bin/phpunit --colors -c phpunit.xml --testsuite integration + - SYMFONY_SERIALIZER=1 vendor/bin/phpunit --colors -c phpunit.xml --testsuite integration + - JMS_BUNDLE=1 DOCTRINE_BUNDLE=1 vendor/bin/phpunit --colors -c phpunit.xml --testsuite integration + - SYMFONY_SERIALIZER=1 DOCTRINE_BUNDLE=1 vendor/bin/phpunit --colors -c phpunit.xml --testsuite integration diff --git a/Adapters/Builtin/BuiltinNormalizerAdapter.php b/Adapters/Builtin/BuiltinNormalizerAdapter.php new file mode 100644 index 0000000..123d430 --- /dev/null +++ b/Adapters/Builtin/BuiltinNormalizerAdapter.php @@ -0,0 +1,18 @@ +hasParameter('jsonrpc_server.jms.handlers')) { + return; + } + + foreach ($container->getParameter('jsonrpc_server.jms.handlers') as $handler => $emid) { + $this->configureRelationHandler($container, $handler, $emid); + } + } + + private function configureRelationHandler(ContainerBuilder $builder, $handler, $emid) + { + if (!$builder->has($emid)) { + return; + } + + $builder->register('jms_serializer.handler.relation.' . $handler, RelationsHandler::class) + ->setArguments([new Reference($emid)]) + ->addTag( + 'jms_serializer.handler', + [ + 'type' => $handler, + 'direction' => 'serialization', + 'format' => 'json', + 'method' => 'serializeRelation', + ] + ) + ->addTag( + 'jms_serializer.handler', + [ + 'type' => $handler, + 'direction' => 'deserialization', + 'format' => 'json', + 'method' => 'deserializeRelation', + ] + ) + ->addTag( + 'jms_serializer.handler', + [ + 'type' => $handler . '', + 'direction' => 'serialization', + 'format' => 'json', + 'method' => 'serializeRelation', + ] + ) + ->addTag( + 'jms_serializer.handler', + [ + 'type' => $handler . '', + 'direction' => 'deserialization', + 'format' => 'json', + 'method' => 'deserializeRelation', + ] + ); + } +} diff --git a/Adapters/JMS/JmsNormalizerAdapter.php b/Adapters/JMS/JmsNormalizerAdapter.php new file mode 100644 index 0000000..c1ab862 --- /dev/null +++ b/Adapters/JMS/JmsNormalizerAdapter.php @@ -0,0 +1,42 @@ +normalizer = $normalizer; + $this->contextFactory = $contextFactory; + } + + /** {@inheritdoc} */ + public function normalize($entity, array $context = []) + { + if (null === $entity) { + return null; + } + + $jmsContext = $this->contextFactory->createSerializationContext(); + $jmsContext->setGroups($context); + + return $this->normalizer->toArray($entity, $jmsContext); + } +} diff --git a/Serializer/RelationsHandler.php b/Adapters/JMS/RelationsHandler.php similarity index 64% rename from Serializer/RelationsHandler.php rename to Adapters/JMS/RelationsHandler.php index 00d16ff..0073623 100644 --- a/Serializer/RelationsHandler.php +++ b/Adapters/JMS/RelationsHandler.php @@ -1,29 +1,30 @@ manager = $manager; } - + public function __construct(ObjectManager $manager) + { + $this->manager = $manager; + } - public function serializeRelation(JsonSerializationVisitor $visitor, $relation, array $type, Context $context) + public function serializeRelation(JsonSerializationVisitor $visitor, $relation) { if ($relation instanceof \Traversable) { $relation = iterator_to_array($relation); @@ -36,24 +37,7 @@ public function serializeRelation(JsonSerializationVisitor $visitor, $relation, return $this->getSingleEntityRelation($relation); } - /** - * @param $relation - * - * @return array|mixed - */ - protected function getSingleEntityRelation($relation) - { - $metadata = $this->manager->getClassMetadata(get_class($relation)); - - $ids = $metadata->getIdentifierValues($relation); - if (!$metadata->isIdentifierComposite) { - $ids = array_shift($ids); - } - - return $ids; - } - - public function deserializeRelation(JsonDeserializationVisitor $visitor, $relation, array $type, Context $context) + public function deserializeRelation(JsonDeserializationVisitor $visitor, $relation, array $type) { $className = isset($type['params'][0]['name']) ? $type['params'][0]['name'] : null; @@ -65,7 +49,7 @@ public function deserializeRelation(JsonDeserializationVisitor $visitor, $relati $metadata = $this->manager->getClassMetadata($className); if (!is_array($relation)) { - return $this->manager->getReference($className, $relation); + return $this->deserializeIdentifier($className, $relation); } $single = false; @@ -77,14 +61,46 @@ public function deserializeRelation(JsonDeserializationVisitor $visitor, $relati } if ($single) { - return $this->manager->getReference($className, $relation); + return $this->deserializeIdentifier($className, $relation); } $objects = []; foreach ($relation as $idSet) { - $objects[] = $this->manager->getReference($className, $idSet); + $objects[] = $this->deserializeIdentifier($className, $idSet); } return $objects; } + + /** + * @param $relation + * + * @return array|mixed + */ + private function getSingleEntityRelation($relation) + { + $metadata = $this->manager->getClassMetadata(get_class($relation)); + + $ids = $metadata->getIdentifierValues($relation); + if (1 === count($metadata->getIdentifierFieldNames())) { + $ids = array_shift($ids); + } + + return $ids; + } + + /** + * @param string $className + * @param mixed $identifier + * + * @return object + */ + private function deserializeIdentifier($className, $identifier) + { + if (method_exists($this->manager, 'getReference')) { + return $this->manager->getReference($className, $identifier); + } + + return $this->manager->find($className, $identifier); + } } diff --git a/Adapters/Symfony/SymfonyNormalizerAdapter.php b/Adapters/Symfony/SymfonyNormalizerAdapter.php new file mode 100644 index 0000000..54b9738 --- /dev/null +++ b/Adapters/Symfony/SymfonyNormalizerAdapter.php @@ -0,0 +1,32 @@ +normalizer = $normalizer; + } + + /** {@inheritdoc} */ + public function normalize($entity, array $context = []) + { + if (null === $entity) { + return null; + } + + return $this->normalizer->normalize($entity, ['groups' => $context]); + } +} diff --git a/BankiruJsonRpcServerBundle.php b/BankiruJsonRpcServerBundle.php new file mode 100644 index 0000000..c289549 --- /dev/null +++ b/BankiruJsonRpcServerBundle.php @@ -0,0 +1,26 @@ +addCompilerPass(new SymfonyAdapterConfigurationPass()); + $container->addCompilerPass(new RelationHandlerPass()); + } + + public function getContainerExtension() + { + return new BankiruJsonRpcServerExtension(); + } +} diff --git a/Controller/JsonRpcController.php b/Controller/JsonRpcController.php index 0064990..8d04266 100644 --- a/Controller/JsonRpcController.php +++ b/Controller/JsonRpcController.php @@ -2,6 +2,7 @@ namespace Bankiru\Api\JsonRpc\Controller; +use Bankiru\Api\JsonRpc\Exception\InvalidRequestException; use Bankiru\Api\JsonRpc\Exception\RpcMethodNotFoundException; use Bankiru\Api\JsonRpc\Http\JsonRpcHttpResponse; use Bankiru\Api\JsonRpc\Specification\JsonRpcRequest; @@ -23,14 +24,15 @@ final class JsonRpcController extends RpcController * * @throws BadRequestHttpException * @throws RpcMethodNotFoundException + * @throws InvalidRequestException */ public function jsonRpcAction(Request $request) { $request->attributes->set('_format', 'json'); $jsonrpc = json_decode($request->getContent()); - if (null === $jsonrpc || json_last_error() !== JSON_ERROR_NONE) { - throw new BadRequestHttpException('Not an valid JSON request'); + if ((!is_array($jsonrpc) && !is_object($jsonrpc)) || json_last_error() !== JSON_ERROR_NONE) { + throw new BadRequestHttpException('Not a valid JSON-RPC request'); } $singleRequest = false; @@ -41,6 +43,9 @@ public function jsonRpcAction(Request $request) $responses = []; foreach ($jsonrpc as $call) { + if (!$call instanceof \stdClass) { + throw InvalidRequestException::notAJsonRpc(); + } $response = $this->handle($call, $request->get('_route')); if (null !== $response) { $responses[] = $response; @@ -59,7 +64,7 @@ public function jsonRpcAction(Request $request) */ protected function getResolver() { - return $this->get('jsonrpc.controller_resolver'); + return $this->get('jsonrpc_server.controller_resolver'); } /** diff --git a/Controller/JsonRpcControllerNameParser.php b/Controller/JsonRpcControllerNameParser.php index a4cb7ed..8aa63be 100644 --- a/Controller/JsonRpcControllerNameParser.php +++ b/Controller/JsonRpcControllerNameParser.php @@ -10,6 +10,6 @@ final class JsonRpcControllerNameParser extends RpcControllerNameParser /** {@inheritdoc} */ protected function guessControllerClassName(BundleInterface $bundle, $controller) { - return $bundle->getNamespace().'\\JsonRpc\\'.$controller.'Controller'; + return $bundle->getNamespace() . '\\JsonRpc\\' . $controller . 'Controller'; } } diff --git a/DependencyInjection/BankiruJsonRpcServerExtension.php b/DependencyInjection/BankiruJsonRpcServerExtension.php new file mode 100644 index 0000000..254438a --- /dev/null +++ b/DependencyInjection/BankiruJsonRpcServerExtension.php @@ -0,0 +1,89 @@ +load('jsonrpc.yml'); + + $config = $this->processConfiguration(new Configuration(), $configs); + + $this->configureSecurity($container); + $this->configureBuiltinAdapter($container); + $this->configureJmsAdapter($container, $config); + + if ($this->isConfigEnabled($container, $config['normalizer_listener'])) { + $container->register('jsonrpc_server.response_listener.normalizer', NormalizingListener::class) + ->setPublic(true) + ->setArguments([new Reference($config['normalizer_listener']['normalizer'])]) + ->addTag( + 'kernel.event_listener', + [ + 'event' => RpcEvents::VIEW, + 'method' => 'onPlainResponse', + 'priority' => 255, + ] + ); + } + } + + public function getAlias() + { + return 'jsonrpc_server'; + } + + /** + * @param ContainerBuilder $container + */ + private function configureBuiltinAdapter(ContainerBuilder $container) + { + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config/adapters')); + + $loader->load('builtin.yml'); + } + + /** + * @param ContainerBuilder $container + * @param array $config + */ + private function configureJmsAdapter(ContainerBuilder $container, array $config) + { + if (!in_array(JMSSerializerBundle::class, $container->getParameter('kernel.bundles'), true)) { + return; + } + + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config/adapters')); + $loader->load('jms.yml'); + + if (!$this->isConfigEnabled($container, $config['adapters']['jms'])) { + return; + } + + $container->setParameter('jsonrpc_server.jms.handlers', (array)$config['adapters']['jms']['relation_handlers']); + } + + /** + * @param ContainerBuilder $container + */ + private function configureSecurity(ContainerBuilder $container) + { + if (in_array(SecurityBundle::class, $container->getParameter('kernel.bundles'), true)) { + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('security.yml'); + } + } +} diff --git a/DependencyInjection/Compiler/JmsDriverPass.php b/DependencyInjection/Compiler/JmsDriverPass.php deleted file mode 100644 index de50fcf..0000000 --- a/DependencyInjection/Compiler/JmsDriverPass.php +++ /dev/null @@ -1,29 +0,0 @@ -has('doctrine')) { - return; - } - - $container->register('jms_serializer.driver.relation', HandledTypeDriver::class) - ->setArguments( - [ - new Reference('jms_serializer.metadata.doctrine_type_driver'), - new Reference('annotation_reader'), - ] - ); - - $container->setAlias('jms_serializer.metadata_driver', 'jms_serializer.driver.relation'); - } -} diff --git a/DependencyInjection/Compiler/SymfonyAdapterConfigurationPass.php b/DependencyInjection/Compiler/SymfonyAdapterConfigurationPass.php new file mode 100644 index 0000000..0486e51 --- /dev/null +++ b/DependencyInjection/Compiler/SymfonyAdapterConfigurationPass.php @@ -0,0 +1,48 @@ +hasDefinition('serializer')) { + return; + } + + if ($this->hasSymfonySerializer($container)) { + $this->configureSymfonyAdapter($container); + } + } + + /** + * @param ContainerBuilder $container + */ + private function configureSymfonyAdapter(ContainerBuilder $container) + { + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../Resources/config/adapters')); + + $loader->load('symfony.yml'); + } + + /** + * @param ContainerBuilder $container + * + * @return bool + */ + private function hasSymfonySerializer(ContainerBuilder $container) + { + $interfaces = class_implements( + $container->getParameterBag()->resolveValue($container->getDefinition('serializer')->getClass()) + ); + + return in_array(NormalizerInterface::class, $interfaces, true); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php new file mode 100644 index 0000000..7a0a878 --- /dev/null +++ b/DependencyInjection/Configuration.php @@ -0,0 +1,53 @@ +root('jsonrpc_server'); + + $normListener = $root + ->children() + ->arrayNode('normalizer_listener') + ->canBeDisabled(); + + $normListener + ->children() + ->scalarNode('normalizer') + ->info('View listener normalizer service ID') + ->defaultValue('jsonrpc_server.builtin_adapter.normalizer'); + + $adapters = $root->children()->arrayNode('adapters')->addDefaultsIfNotSet(); + $this->configureJms($adapters); + + return $builder; + } + + /** + * @param ArrayNodeDefinition $adapters + */ + private function configureJms(ArrayNodeDefinition $adapters) + { + $jms = $adapters->children()->arrayNode('jms'); + + $jms->canBeEnabled(); + + $jms->children() + ->arrayNode('relation_handlers') + ->fixXmlConfig('relation_handler') + ->useAttributeAsKey('handler') + ->prototype('scalar') + ->info( + 'Key: Relation handler name (i.e. "Relation"), Value: service ID for relation handler entity manager' + ) + ->isRequired(); + } +} diff --git a/DependencyInjection/JsonRpcExtension.php b/DependencyInjection/JsonRpcExtension.php deleted file mode 100644 index 3d587af..0000000 --- a/DependencyInjection/JsonRpcExtension.php +++ /dev/null @@ -1,68 +0,0 @@ -load('jsonrpc.yml'); - } - - /** {@inheritdoc} */ - public function process(ContainerBuilder $container) - { - if (!$container->has('doctrine')) { - return; - } - - $container->register('jms_serializer.handler.relation', RelationsHandler::class) - ->setArguments([new Reference('doctrine.orm.entity_manager')]) - ->addTag( - 'jms_serializer.handler', - [ - 'type' => 'Relation', - 'direction' => 'serialization', - 'format' => 'json', - 'method' => 'serializeRelation', - ] - ) - ->addTag( - 'jms_serializer.handler', - [ - 'type' => 'Relation', - 'direction' => 'deserialization', - 'format' => 'json', - 'method' => 'deserializeRelation', - ] - ) - ->addTag( - 'jms_serializer.handler', - [ - 'type' => 'Relation', - 'direction' => 'serialization', - 'format' => 'json', - 'method' => 'serializeRelation', - ] - ) - ->addTag( - 'jms_serializer.handler', - [ - 'type' => 'Relation', - 'direction' => 'deserialization', - 'format' => 'json', - 'method' => 'deserializeRelation', - ] - ); - } -} diff --git a/Exception/InvalidRequestException.php b/Exception/InvalidRequestException.php index 3d7b08d..8262aa3 100644 --- a/Exception/InvalidRequestException.php +++ b/Exception/InvalidRequestException.php @@ -21,4 +21,12 @@ public static function invalidVersion($expected, $actual) sprintf('Invalid JSONRPC 2.0 Request. Version mismatch: %s expected, %s given', $expected, $actual) ); } + + public static function notAJsonRpc() + { + return self::create( + JsonRpcError::INVALID_REQUEST, + 'Not a JSONRPC 2.0 Request' + ); + } } diff --git a/Exception/JsonRpcException.php b/Exception/JsonRpcException.php index b27698a..d0687e7 100644 --- a/Exception/JsonRpcException.php +++ b/Exception/JsonRpcException.php @@ -18,7 +18,6 @@ public function __construct($message = '', $code = 0, Exception $previous = null parent::__construct($message, $code, $previous); } - /** * @return JsonRpcErrorInterface */ diff --git a/Http/JsonRpcHttpResponse.php b/Http/JsonRpcHttpResponse.php index 3291d46..af333c8 100644 --- a/Http/JsonRpcHttpResponse.php +++ b/Http/JsonRpcHttpResponse.php @@ -2,7 +2,7 @@ namespace Bankiru\Api\JsonRpc\Http; -use Bankiru\Api\JsonRpc\JsonRpcBundle; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use ScayTrase\Api\JsonRpc\JsonRpcErrorInterface; use ScayTrase\Api\JsonRpc\JsonRpcResponseInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -24,18 +24,13 @@ public function __construct($jsonRpc = null, $status = 200, array $headers = []) return; } - if (!is_array($jsonRpc)) { - parent::__construct($this->formatJsonRpcResponse($jsonRpc), $status, $headers); + if (is_array($jsonRpc)) { + parent::__construct(array_map([$this, 'formatJsonRpcResponse'], $jsonRpc), $status, $headers); return; } - $data = []; - foreach ($jsonRpc as $response) { - $data[] = $this->formatJsonRpcResponse($response); - } - - parent::__construct($data, $status, $headers); + parent::__construct($this->formatJsonRpcResponse($jsonRpc), $status, $headers); } /** @@ -46,21 +41,21 @@ public function __construct($jsonRpc = null, $status = 200, array $headers = []) private function formatJsonRpcResponse(JsonRpcResponseInterface $jsonRpc) { $data = [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'id' => $jsonRpc->getId(), ]; if ($jsonRpc->isSuccessful()) { $data['result'] = $jsonRpc->getBody(); - return $data; - } else { - $error = $jsonRpc->getError(); - $data['error']['code'] = $error->getCode(); - $data['error']['message'] = $error->getMessage(); - $data['error']['data'] = $error instanceof JsonRpcErrorInterface ? $error->getData() : null; - return $data; } + + $error = $jsonRpc->getError(); + $data['error']['code'] = $error->getCode(); + $data['error']['message'] = $error->getMessage(); + $data['error']['data'] = $error instanceof JsonRpcErrorInterface ? $error->getData() : null; + + return $data; } } diff --git a/JsonRpcBundle.php b/JsonRpcBundle.php deleted file mode 100644 index 554cdb9..0000000 --- a/JsonRpcBundle.php +++ /dev/null @@ -1,19 +0,0 @@ -addCompilerPass(new JmsDriverPass(), PassConfig::TYPE_BEFORE_REMOVING); - } -} diff --git a/Listener/AccessDeniedExceptionListener.php b/Listener/AccessDeniedExceptionListener.php new file mode 100644 index 0000000..f88907e --- /dev/null +++ b/Listener/AccessDeniedExceptionListener.php @@ -0,0 +1,34 @@ +getRequest(); + if (!$request instanceof JsonRpcRequestInterface) { + return; + } + + $exception = $event->getException(); + + if (!$exception instanceof AccessDeniedException) { + return; + } + + $event->setException( + JsonRpcException::create( + JsonRpcError::METHOD_NOT_FOUND, + $exception->getMessage(), + $exception->getTrace() + ) + ); + } +} diff --git a/Listener/ExceptionHandlerListener.php b/Listener/ExceptionHandlerListener.php index 535b5bc..0283f00 100644 --- a/Listener/ExceptionHandlerListener.php +++ b/Listener/ExceptionHandlerListener.php @@ -13,15 +13,17 @@ final class ExceptionHandlerListener { - private $debug = false; + private $debug; /** * ExceptionHandlerListener constructor. * * @param bool $debug */ - public function __construct($debug) { $this->debug = (bool)$debug; } - + public function __construct($debug = false) + { + $this->debug = (bool)$debug; + } public function onJsonRpcException(GetExceptionResponseEvent $event) { @@ -31,18 +33,21 @@ public function onJsonRpcException(GetExceptionResponseEvent $event) } $exception = $event->getException(); + if ($exception instanceof InvalidMethodParametersException) { $exception = JsonRpcException::create( JsonRpcError::INVALID_PARAMS, $exception->getMessage(), - $exception->getTrace()); + $exception->getTrace() + ); } elseif ($exception instanceof MethodNotFoundException) { $exception = JsonRpcException::create( JsonRpcError::METHOD_NOT_FOUND, $exception->getMessage(), - $exception->getTrace()); + $exception->getTrace() + ); } if ($exception instanceof JsonRpcExceptionInterface) { diff --git a/Listener/HttpExceptionListener.php b/Listener/HttpExceptionListener.php index 40018f8..fe9a0ca 100644 --- a/Listener/HttpExceptionListener.php +++ b/Listener/HttpExceptionListener.php @@ -6,20 +6,8 @@ use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -class HttpExceptionListener +final class HttpExceptionListener { - private $debug = false; - - /** - * ExceptionHandlerListener constructor. - * - * @param bool $debug - */ - public function __construct($debug) - { - $this->debug = (bool)$debug; - } - public function onJsonRpcException(GetResponseForExceptionEvent $event) { $exception = $event->getException(); diff --git a/Listener/NormalizingListener.php b/Listener/NormalizingListener.php new file mode 100644 index 0000000..94d1dba --- /dev/null +++ b/Listener/NormalizingListener.php @@ -0,0 +1,49 @@ +normalizer = $normalizer; + } + + public function onPlainResponse(ViewEvent $event) + { + $request = $event->getRequest(); + $response = $event->getResponse(); + + // No need to perform JSON-RPC serialization here + if (!$request instanceof RichJsonRpcRequest || $request->isNotification()) { + return; + } + + // Response is already properly formatted + if ($response instanceof JsonRpcResponseInterface) { + return; + } + + $content = $event->getResponse(); + if (!is_scalar($content) && null !== $content) { + $content = $this->normalizer->normalize($content, $request->getAttributes()->get('_context')); + } + + $event->setResponse($content); + } +} diff --git a/Listener/NotificationResponseListener.php b/Listener/NotificationResponseListener.php index a04bf60..a2e9c1b 100644 --- a/Listener/NotificationResponseListener.php +++ b/Listener/NotificationResponseListener.php @@ -17,7 +17,7 @@ final class NotificationResponseListener */ public function onNullResponse(ViewEvent $event) { - $request = $event->getRequest(); + $request = $event->getRequest(); if (!$request instanceof JsonRpcRequestInterface) { return; diff --git a/Listener/SerializedJsonRpcResponseListener.php b/Listener/SerializedJsonRpcResponseListener.php deleted file mode 100644 index 68f9c6f..0000000 --- a/Listener/SerializedJsonRpcResponseListener.php +++ /dev/null @@ -1,86 +0,0 @@ -serializer = $serializer; } - - public function onPlainResponse(ViewEvent $event) - { - $request = $event->getRequest(); - $response = $event->getResponse(); - - // No need to perform JSON-RPC serialization here - if (!$request instanceof JsonRpcRequestInterface || $request->isNotification()) { - return; - } - - // Response is already properly formatted - if ($response instanceof JsonRpcResponseInterface) { - return; - } - - $response = new JsonRpcResponse( - $request->getId(), - json_decode( - $this->serializer->serialize( - $event->getResponse(), - 'json', - $this->createSerializerContext($event) - ) - ) - ); - $event->setResponse($response); - - } - - /** - * @param ViewEvent $event - * - * @return SerializationContext - * @throws \LogicException - */ - private function createSerializerContext(ViewEvent $event) - { - $context = SerializationContext::create(); - $context->setSerializeNull(true); - $attributes = $event->getRequest()->getAttributes(); - $defaults = $attributes->get('_with_default_context', true); - - if (!$defaults && (false === $attributes->get('_context', false))) { - throw new \LogicException( - 'Could not perform object serialization as no default context allowed and no custom set' - ); - } - - $groups = []; - if ($defaults) { - $groups[] = 'Default'; - } - if (false !== $attributes->get('_context', false)) { - foreach ((array)$attributes->get('_context') as $group) { - $groups[] = $group; - } - } - - $context->setGroups($groups); - - return $context; - } -} diff --git a/Listener/ViewListener.php b/Listener/ViewListener.php new file mode 100644 index 0000000..1c951bc --- /dev/null +++ b/Listener/ViewListener.php @@ -0,0 +1,31 @@ +getRequest(); + $response = $event->getResponse(); + + // No need to perform JSON-RPC serialization here + if (!$request instanceof RichJsonRpcRequest || $request->isNotification()) { + return; + } + + // Response is already properly formatted + if ($response instanceof JsonRpcResponseInterface) { + return; + } + + $response = new JsonRpcResponse($request->getId(), $response); + + $event->setResponse($response); + } +} diff --git a/NormalizerInterface.php b/NormalizerInterface.php new file mode 100644 index 0000000..4ed0fd0 --- /dev/null +++ b/NormalizerInterface.php @@ -0,0 +1,16 @@ +setJsonRpcError( new JsonRpcError( @@ -44,9 +49,31 @@ $execption->setJsonRpcError( (object)['debug_data' => 'some debug data'] ) ); +``` + +Some exceptions are handled in a more specific manner + +* `AccessDeniedException` will result in `-32601` (method not found) json-rpc error code. +* `MethodNotFoundException` will result in `-32601` (method not found) json-rpc error code. +* `InvalidMethodParametersException` will result in `-32602` (invalid params) json-rpc error code + +## Configuration + +Configuration reference: + +```yaml +jsonrpc_server: + + # Normalizer listener service ID + normalizer_listener: jsonrpc_server.builtin_adapter.view_listener + adapters: + jms: + relation_handlers: + # Prototype: Key: Relation handler name (i.e. "Relation"), Value: service ID for relation handler entity manager + handler: ~ ``` ## Specification -Refer to official JSON-RPC 2.0 specification at http://www.jsonrpc.org/specification +Refer to official JSON-RPC 2.0 specification at http://www.jsonrpc.org/specification diff --git a/Resources/config/adapters/builtin.yml b/Resources/config/adapters/builtin.yml new file mode 100644 index 0000000..1add456 --- /dev/null +++ b/Resources/config/adapters/builtin.yml @@ -0,0 +1,4 @@ +services: + jsonrpc_server.builtin_adapter.normalizer: + class: Bankiru\Api\JsonRpc\Adapters\Builtin\BuiltinNormalizerAdapter + public: false diff --git a/Resources/config/adapters/jms.yml b/Resources/config/adapters/jms.yml new file mode 100644 index 0000000..65159d3 --- /dev/null +++ b/Resources/config/adapters/jms.yml @@ -0,0 +1,10 @@ +parameters: + jsonrpc_server.jms.handlers: [] + +services: + jsonrpc_server.jms_adapter.normalizer: + class: Bankiru\Api\JsonRpc\Adapters\JMS\JmsNormalizerAdapter + arguments: + - "@jms_serializer" + - "@jms_serializer.serialization_context_factory" + public: false diff --git a/Resources/config/adapters/symfony.yml b/Resources/config/adapters/symfony.yml new file mode 100644 index 0000000..16a49d1 --- /dev/null +++ b/Resources/config/adapters/symfony.yml @@ -0,0 +1,5 @@ +services: + jsonrpc_server.symfony_adapter.normalizer: + class: Bankiru\Api\JsonRpc\Adapters\Symfony\SymfonyNormalizerAdapter + arguments: + - "@serializer" diff --git a/Resources/config/jsonrpc.yml b/Resources/config/jsonrpc.yml index 28f4155..76511f4 100644 --- a/Resources/config/jsonrpc.yml +++ b/Resources/config/jsonrpc.yml @@ -1,47 +1,47 @@ +parameters: + jsonrpc_server.debug: false + services: - jsonrpc.controller_resolver: + jsonrpc_server.controller_resolver: class: Bankiru\Api\Rpc\Routing\ControllerResolver\ControllerResolver arguments: - "@service_container" - - "@jsonrpc.controller_name_parser" + - "@jsonrpc_server.controller_name_parser" - "@?logger" - jsonrpc.view.notification_response: + jsonrpc_server.view.notification_response: class: Bankiru\Api\JsonRpc\Listener\NotificationResponseListener tags: - { name: kernel.event_listener, event: rpc.view, method: onNullResponse, priority: -255 } - jsonrpc.view.notification_filter: + jsonrpc_server.view.notification_filter: class: Bankiru\Api\JsonRpc\Listener\NotificationFilter tags: - { name: kernel.event_listener, event: rpc.response, method: filterNotificationResponse, priority: -255 } - jsonrpc.view.serialize_response: - class: Bankiru\Api\JsonRpc\Listener\SerializedJsonRpcResponseListener - arguments: - - "@jms_serializer" + jsonrpc_server.plain_view_listener: + class: Bankiru\Api\JsonRpc\Listener\ViewListener tags: - - { name: kernel.event_listener, event: rpc.view, method: onPlainResponse, priority: -254 } + - { name: kernel.event_listener, event: rpc.view, method: onPlainResponse, priority: -255 } - jsonrpc.reponse_listener.match_id: + jsonrpc_server.response_listener.match_id: class: Bankiru\Api\JsonRpc\Listener\IdMatcherListener tags: - { name: kernel.event_listener, event: rpc.response, method: onFilterResponse, priority: -254 } - jsonrpc.controller_name_parser: + jsonrpc_server.controller_name_parser: class: Bankiru\Api\JsonRpc\Controller\JsonRpcControllerNameParser - parent: rpc.controller_name_parser + parent: rpc_server.controller_name_parser - jsonrpc.exception_listener: + jsonrpc_server.exception_listener: class: Bankiru\Api\JsonRpc\Listener\ExceptionHandlerListener arguments: - - "%kernel.debug%" + - "%jsonrpc_server.debug%" tags: - { name: kernel.event_listener, event: rpc.exception, method: onJsonRpcException } - jsonrpc.http_exception_listener: + jsonrpc_server.http_exception_listener: class: Bankiru\Api\JsonRpc\Listener\HttpExceptionListener - arguments: - - "%kernel.debug%" tags: - { name: kernel.event_listener, event: kernel.exception, method: onJsonRpcException } + diff --git a/Resources/config/security.yml b/Resources/config/security.yml new file mode 100644 index 0000000..46a93ea --- /dev/null +++ b/Resources/config/security.yml @@ -0,0 +1,5 @@ +services: + jsonrpc_server.security.exception_listener: + class: Bankiru\Api\JsonRpc\Listener\AccessDeniedExceptionListener + tags: + - { name: kernel.event_listener, event: rpc.exception, method: onRpcException, priority: 255 } diff --git a/Serializer/HandledType.php b/Serializer/HandledType.php deleted file mode 100644 index e691ed8..0000000 --- a/Serializer/HandledType.php +++ /dev/null @@ -1,30 +0,0 @@ -handler = $handler; } - - /** - * @return string - */ - public function getHandler() - { - return $this->handler; - } -} diff --git a/Serializer/HandledTypeDriver.php b/Serializer/HandledTypeDriver.php deleted file mode 100644 index 3d3d4fd..0000000 --- a/Serializer/HandledTypeDriver.php +++ /dev/null @@ -1,71 +0,0 @@ -driver = $driver; - $this->reader = $reader; - } - - /** - * @param \ReflectionClass $class - * - * @return \Metadata\ClassMetadata - */ - public function loadMetadataForClass(\ReflectionClass $class) - { - $metadata = $this->driver->loadMetadataForClass($class); - foreach ($metadata->propertyMetadata as $key => $propertyMetadata) { - $type = $propertyMetadata->type['name']; - - if (!$propertyMetadata->reflection) { - continue; - } - - /** @var PropertyMetadata $propertyMetadata */ - /** @var HandledType $annot */ - $annot = $this->reader->getPropertyAnnotation($propertyMetadata->reflection, HandledType::class); - if (!$annot) { - continue; - } - - $isCollection = false; - $collectionType = null; - - if (in_array($type, ['array', 'ArrayCollection'], true)) { - $isCollection = true; - $collectionType = $type; - $type = $propertyMetadata->type['params'][0]['name']; - } - - $handler = $annot->handler ?: 'Relation'; - - $newType = sprintf('%s<%s>', $handler, $type); - if ($isCollection) { - $newType = sprintf('%s<%s<%s>>', $collectionType, $handler, $type); - } - - $propertyMetadata->setType($newType); - } - - return $metadata; - } -} diff --git a/Specification/JsonRpcRequest.php b/Specification/JsonRpcRequest.php index f04b0bc..5ba298d 100644 --- a/Specification/JsonRpcRequest.php +++ b/Specification/JsonRpcRequest.php @@ -2,12 +2,12 @@ namespace Bankiru\Api\JsonRpc\Specification; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use Bankiru\Api\JsonRpc\Exception\InvalidRequestException; use Bankiru\Api\JsonRpc\Exception\JsonRpcException; -use Bankiru\Api\JsonRpc\JsonRpcBundle; -use ScayTrase\Api\JsonRpc\JsonRpcError; use ScayTrase\Api\JsonRpc\JsonRpcRequestInterface; +/** @internal */ final class JsonRpcRequest implements JsonRpcRequestInterface { /** @var string|null */ @@ -17,12 +17,6 @@ final class JsonRpcRequest implements JsonRpcRequestInterface /** @var mixed|\stdClass */ private $parameters; - /** @return bool True if request should not receive response from the server */ - public function isNotification() - { - return null === $this->id; - } - /** * @param \stdClass $source * @@ -44,8 +38,11 @@ public static function fromStdClass(\stdClass $source) throw InvalidRequestException::missingFields($missing); } - if (JsonRpcBundle::VERSION !== $source->jsonrpc) { - throw InvalidRequestException::invalidVersion(JsonRpcBundle::VERSION, $source->jsonrpc); + if (BankiruJsonRpcServerBundle::JSONRPC_VERSION !== $source->jsonrpc) { + throw InvalidRequestException::invalidVersion( + BankiruJsonRpcServerBundle::JSONRPC_VERSION, + $source->jsonrpc + ); } $request->id = isset($source->id) ? $source->id : null; @@ -55,6 +52,12 @@ public static function fromStdClass(\stdClass $source) return $request; } + /** {@inheritdoc} */ + public function isNotification() + { + return null === $this->id; + } + /** {@inheritdoc} */ public function jsonSerialize() { @@ -69,7 +72,7 @@ public function jsonSerialize() /** {@inheritdoc} */ public function getVersion() { - return JsonRpcBundle::VERSION; + return BankiruJsonRpcServerBundle::JSONRPC_VERSION; } /** @return string */ @@ -78,13 +81,13 @@ public function getMethod() return $this->method; } - /** @return array */ + /** {@inheritdoc} */ public function getParameters() { return $this->parameters; } - /** @return int|null Id. if not a notification and id is not set - id should be automatically generated */ + /** {@inheritdoc} */ public function getId() { return $this->id; diff --git a/Specification/JsonRpcResponse.php b/Specification/JsonRpcResponse.php index 8edaefe..3ee27e4 100644 --- a/Specification/JsonRpcResponse.php +++ b/Specification/JsonRpcResponse.php @@ -2,10 +2,11 @@ namespace Bankiru\Api\JsonRpc\Specification; -use Bankiru\Api\JsonRpc\JsonRpcBundle; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use ScayTrase\Api\JsonRpc\JsonRpcResponseInterface; use ScayTrase\Api\Rpc\RpcErrorInterface; +/** @internal */ final class JsonRpcResponse implements JsonRpcResponseInterface { /** @var string */ @@ -29,22 +30,17 @@ public function __construct($id = null, $body = null, RpcErrorInterface $error = $this->error = $error; } - - /** - * @return string JSON-RPC version - */ + /** {@inheritdoc} */ public function getVersion() { - return JsonRpcBundle::VERSION; + return BankiruJsonRpcServerBundle::JSONRPC_VERSION; } - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ public function jsonSerialize() { $result = [ - self::VERSION_FIELD => JsonRpcBundle::VERSION, + self::VERSION_FIELD => BankiruJsonRpcServerBundle::JSONRPC_VERSION, self::ID_FIELD => $this->getId(), ]; @@ -59,25 +55,25 @@ public function jsonSerialize() return $result; } - /** @return string|null Response ID or null for notification pseudo-response */ + /** {@inheritdoc} */ public function getId() { return $this->id; } - /** @return bool */ + /** {@inheritdoc} */ public function isSuccessful() { return $this->getError() === null; } - /** @return RpcErrorInterface|null */ + /** {@inheritdoc} */ public function getError() { return $this->error; } - /** @return \stdClass|\stdClass[]|null */ + /** {@inheritdoc} */ public function getBody() { return $this->body; diff --git a/Specification/RichJsonRpcRequest.php b/Specification/RichJsonRpcRequest.php index a462ee5..6c3e384 100644 --- a/Specification/RichJsonRpcRequest.php +++ b/Specification/RichJsonRpcRequest.php @@ -2,12 +2,12 @@ namespace Bankiru\Api\JsonRpc\Specification; -use Bankiru\Api\JsonRpc\JsonRpcBundle; -use Bankiru\Api\Rpc\Http\RequestInterface; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; +use Bankiru\Api\Rpc\RpcRequestInterface; use ScayTrase\Api\JsonRpc\JsonRpcRequestInterface; use Symfony\Component\HttpFoundation\ParameterBag; -final class RichJsonRpcRequest implements RequestInterface, JsonRpcRequestInterface +final class RichJsonRpcRequest implements RpcRequestInterface, JsonRpcRequestInterface { /** @var JsonRpcRequestInterface */ private $request; @@ -23,11 +23,7 @@ final class RichJsonRpcRequest implements RequestInterface, JsonRpcRequestInterf public function __construct(JsonRpcRequestInterface $request, ParameterBag $attributes = null) { $this->request = $request; - $this->attributes = $attributes; - - if (null === $this->attributes) { - $this->attributes = new ParameterBag(); - } + $this->attributes = $attributes ?: new ParameterBag(); } /** {@inheritdoc} */ @@ -56,7 +52,7 @@ public function jsonSerialize() /** {@inheritdoc} */ public function getVersion() { - return JsonRpcBundle::VERSION; + return BankiruJsonRpcServerBundle::JSONRPC_VERSION; } /** {@inheritdoc} */ diff --git a/Test/DependencyInjection/JsonRpcTestExtension.php b/Test/DependencyInjection/JsonRpcTestExtension.php deleted file mode 100644 index 590ff18..0000000 --- a/Test/DependencyInjection/JsonRpcTestExtension.php +++ /dev/null @@ -1,58 +0,0 @@ -getExtensions() as $name => $extension) { - switch ($name) { - case 'rpc': - $container->prependExtensionConfig( - $name, - [ - 'router' => [ - 'endpoints' => [ - 'test' => [ - 'path' => '/test/', - 'resources' => '@JsonRpcTestBundle/Resources/config/jsonrpc_routes.yml', - 'defaults' => [ - '_controller' => 'JsonRpcBundle:JsonRpc:jsonRpc', - '_format' => 'json', - ], - ], - 'test_private' => [ - 'path' => '/test/private/', - 'defaults' => [ - '_controller' => 'JsonRpcBundle:JsonRpc:jsonRpc', - '_format' => 'json', - ], - 'resources' => [ - '@JsonRpcTestBundle/Resources/config/jsonrpc_routes.yml', - '@JsonRpcTestBundle/Resources/config/jsonrpc_private.yml', - ], - ], - ], - ], - ] - ); - break; - } - } - } -} diff --git a/Test/Entity/SampleEntity.php b/Test/Entity/SampleEntity.php index 412929a..ce679db 100644 --- a/Test/Entity/SampleEntity.php +++ b/Test/Entity/SampleEntity.php @@ -2,33 +2,14 @@ namespace Bankiru\Api\JsonRpc\Test\Entity; -use JMS\Serializer\Annotation\Expose; -use JMS\Serializer\Annotation\Groups; -use JMS\Serializer\Annotation\SerializedName; -use JMS\Serializer\Annotation\Since; -use JMS\Serializer\Annotation\Type; - -class SampleEntity +class SampleEntity implements \JsonSerializable { - /** - * @var int|null - */ + /** @var int|null */ private $id; - /** - * @var string - * @Type("string") - * @SerializedName("sample_payload") - * @Expose() - */ + /** @var string */ private $payload; - /** - * @var string - * @Type("string") - * @SerializedName("private_payload") - * @Expose() - * @Groups({"private"}) - * @Since("0.1") - */ + + /** @var string */ private $secret; /** @@ -57,4 +38,13 @@ public function getPayload() { return $this->payload; } + + /** {@inheritdoc} */ + public function jsonSerialize() + { + return [ + 'id' => $this->id, + 'sample_payload' => $this->payload, + ]; + } } diff --git a/Test/JsonRpc/AnnotationController.php b/Test/JsonRpc/AnnotationController.php index 7580c1e..2b78c39 100644 --- a/Test/JsonRpc/AnnotationController.php +++ b/Test/JsonRpc/AnnotationController.php @@ -6,12 +6,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; /** - * Class AnnotationController - * - * @package Bankiru\Api\JsonRpc\Test\JsonRpc * @Method("annotation") */ -class AnnotationController extends Controller +final class AnnotationController extends Controller { /** * @return array diff --git a/Test/JsonRpc/FailingController.php b/Test/JsonRpc/FailingController.php index d14d518..9d0a132 100644 --- a/Test/JsonRpc/FailingController.php +++ b/Test/JsonRpc/FailingController.php @@ -2,7 +2,7 @@ namespace Bankiru\Api\JsonRpc\Test\JsonRpc; -class FailingController +final class FailingController { public function failureAction() { diff --git a/Test/JsonRpc/SecurityController.php b/Test/JsonRpc/SecurityController.php new file mode 100644 index 0000000..990441a --- /dev/null +++ b/Test/JsonRpc/SecurityController.php @@ -0,0 +1,30 @@ + true]; + } + + /** + * @Method("private") + * @Security("is_granted('IS_AUTHENTICATED_FULLY')") + */ + public function privateAction() + { + return ['success' => true]; + } +} diff --git a/Test/JsonRpc/TestController.php b/Test/JsonRpc/TestController.php index a41c980..2405f2e 100644 --- a/Test/JsonRpc/TestController.php +++ b/Test/JsonRpc/TestController.php @@ -6,7 +6,7 @@ use Bankiru\Api\JsonRpc\Test\Entity\SampleEntity; use ScayTrase\Api\JsonRpc\JsonRpcRequestInterface; -class TestController +final class TestController { public function sampleAction(JsonRpcRequestInterface $request) { diff --git a/Test/JsonRpcTestBundle.php b/Test/JsonRpcTestBundle.php index 912ea2c..ccf8185 100644 --- a/Test/JsonRpcTestBundle.php +++ b/Test/JsonRpcTestBundle.php @@ -4,6 +4,7 @@ use Symfony\Component\HttpKernel\Bundle\Bundle; -class JsonRpcTestBundle extends Bundle +/** @internal */ +final class JsonRpcTestBundle extends Bundle { } diff --git a/Test/Kernel/TestKernel.php b/Test/Kernel/TestKernel.php new file mode 100644 index 0000000..a904a33 --- /dev/null +++ b/Test/Kernel/TestKernel.php @@ -0,0 +1,90 @@ +getName() . '/cache'; + } + + public function getLogDir() + { + return __DIR__ . '/../../build/' . $this->getName() . '/log'; + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + $loader->load(__DIR__ . '/config.yml'); + + if (false !== getenv('JMS_BUNDLE')) { + $loader->load(__DIR__ . '/config_jms.yml'); + } + + if (false !== getenv('SYMFONY_SERIALIZER')) { + $loader->load(__DIR__ . '/config_symfony.yml'); + } + + if (false !== getenv('DOCTRINE_BUNDLE')) { + $loader->load(__DIR__ . '/config_doctrine.yml'); + } + } + + public function getName() + { + $name = 'jsorpc_test'; + + if (false !== getenv('JMS_BUNDLE')) { + $name .= '_jms'; + } + + if (false !== getenv('SYMFONY_SERIALIZER')) { + $name .= '_symfony'; + } + + if (false !== getenv('DOCTRINE_BUNDLE')) { + $name .= '_doctrine'; + } + + return $name; + } + + public function getEnvironment() + { + return 'test'; + } +} diff --git a/Test/Kernel/config.yml b/Test/Kernel/config.yml new file mode 100644 index 0000000..d0ce868 --- /dev/null +++ b/Test/Kernel/config.yml @@ -0,0 +1,47 @@ +framework: + test: ~ + secret: test + router: + resource: "%kernel.root_dir%/routing.yml" + +services: + logger: + class: Psr\Log\NullLogger + +security: + providers: + in_memory: + memory: ~ + + firewalls: + main: + anonymous: ~ + + access_control: + - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY } + +jsonrpc_server: + normalizer_listener: + enabled: true + adapters: + jms: + relation_handlers: + Relation: doctrine.orm.entity_manager + +rpc_server: + router: + endpoints: + test: + path: /test/ + resources: '@JsonRpcTestBundle/Resources/config/jsonrpc_routes.yml' + defaults: + _controller : BankiruJsonRpcServerBundle:JsonRpc:jsonRpc + _format: json + test_private: + path: /test/private/ + defaults: + _controller : BankiruJsonRpcServerBundle:JsonRpc:jsonRpc + _format: json + resources: + - '@JsonRpcTestBundle/Resources/config/jsonrpc_routes.yml' + - '@JsonRpcTestBundle/Resources/config/jsonrpc_private.yml' diff --git a/Test/Kernel/config_doctrine.yml b/Test/Kernel/config_doctrine.yml new file mode 100644 index 0000000..4daa5a5 --- /dev/null +++ b/Test/Kernel/config_doctrine.yml @@ -0,0 +1,7 @@ +doctrine: + dbal: + driver: pdo_sqlite + memory: true + orm: + auto_mapping: true + auto_generate_proxy_classes: true diff --git a/Test/Kernel/config_jms.yml b/Test/Kernel/config_jms.yml new file mode 100644 index 0000000..fa7a916 --- /dev/null +++ b/Test/Kernel/config_jms.yml @@ -0,0 +1,10 @@ +framework: + translator: ~ + +jms_serializer: + metadata: + auto_detection: true + +jsonrpc_server: + normalizer_listener: + normalizer: jsonrpc_server.jms_adapter.normalizer diff --git a/Test/Kernel/config_symfony.yml b/Test/Kernel/config_symfony.yml new file mode 100644 index 0000000..440a8b3 --- /dev/null +++ b/Test/Kernel/config_symfony.yml @@ -0,0 +1,6 @@ +framework: + serializer: ~ + +jsonrpc_server: + normalizer_listener: + normalizer: jsonrpc_server.symfony_adapter.normalizer diff --git a/Test/Tests/Fixtures/routing.yml b/Test/Kernel/routing.yml similarity index 100% rename from Test/Tests/Fixtures/routing.yml rename to Test/Kernel/routing.yml diff --git a/Test/Resources/config/doctrine/SampleEntity.orm.yml b/Test/Resources/config/doctrine/SampleEntity.orm.yml new file mode 100644 index 0000000..48d9977 --- /dev/null +++ b/Test/Resources/config/doctrine/SampleEntity.orm.yml @@ -0,0 +1,10 @@ +Bankiru\Api\JsonRpc\Test\Entity\SampleEntity: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + payload: {type: string} + secret: {type: string} diff --git a/Test/Resources/config/serializer/Entity.SampleEntity.yml b/Test/Resources/config/serializer/Entity.SampleEntity.yml new file mode 100644 index 0000000..bbfff37 --- /dev/null +++ b/Test/Resources/config/serializer/Entity.SampleEntity.yml @@ -0,0 +1,19 @@ +Bankiru\Api\JsonRpc\Test\Entity\SampleEntity: + exclusion_policy: ALL + properties: + id: + type: integer + serialized_name: id + expose: true + + payload: + type: string + serialized_name: sample_payload + expose: true + + secret: + type: string + serialized_name: private_payload + groups: [private] + since_version: 0.1 + expose: true diff --git a/Test/Tests/AnnotatedControllerTest.php b/Test/Tests/AnnotatedControllerTest.php index 74691c5..95dc5f0 100644 --- a/Test/Tests/AnnotatedControllerTest.php +++ b/Test/Tests/AnnotatedControllerTest.php @@ -2,12 +2,10 @@ namespace Bankiru\Api\JsonRpc\Test\Tests; -use Bankiru\Api\JsonRpc\JsonRpcBundle; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use ScayTrase\Api\JsonRpc\SyncResponse; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -class AnnotatedControllerTest extends JsonRpcTestCase +final class AnnotatedControllerTest extends JsonRpcTestCase { public function testNestedContext() { @@ -16,7 +14,7 @@ public function testNestedContext() $client, '/test/private/', [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'prefix/annotation/sub', 'id' => 'test', 'params' => [ @@ -30,16 +28,16 @@ public function testNestedContext() self::assertInstanceOf(\stdClass::class, $content); $jsonResponse = new SyncResponse($content); - self::assertTrue($jsonResponse->isSuccessful()); + self::assertTrue($jsonResponse->isSuccessful(), json_encode($content, JSON_PRETTY_PRINT)); } /** * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException - * @expectedExceptionMessage Not an valid JSON request + * @expectedExceptionMessage Not a valid JSON-RPC request */ public function testEmptyRequest() { - $client = self::createClient(); + $client = self::createClient(); $this->sendRequest( $client, '/test/private/', diff --git a/Test/Tests/EntityConversionTest.php b/Test/Tests/EntityConversionTest.php index 9174500..952a11d 100644 --- a/Test/Tests/EntityConversionTest.php +++ b/Test/Tests/EntityConversionTest.php @@ -2,10 +2,10 @@ namespace Bankiru\Api\JsonRpc\Test\Tests; -use Bankiru\Api\JsonRpc\JsonRpcBundle; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use ScayTrase\Api\JsonRpc\SyncResponse; -class EntityConversionTest extends JsonRpcTestCase +final class EntityConversionTest extends JsonRpcTestCase { public function testEntitySerialization() { @@ -13,7 +13,7 @@ public function testEntitySerialization() self::createClient(), '/test/', [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'entity', 'id' => 'test', 'params' => [ @@ -28,10 +28,13 @@ public function testEntitySerialization() $jsonResponse = new SyncResponse($content); self::assertTrue($jsonResponse->isSuccessful(), json_encode($content, JSON_PRETTY_PRINT)); - self::assertTrue(isset($jsonResponse->getBody()->{'sample_payload'})); + self::assertTrue(isset($jsonResponse->getBody()->{'sample_payload'}), json_encode($content, JSON_PRETTY_PRINT)); self::assertEquals('my-payload', $jsonResponse->getBody()->{'sample_payload'}); - self::assertFalse(isset($jsonResponse->getBody()->{'private_payload'})); + self::assertFalse( + isset($jsonResponse->getBody()->{'private_payload'}), + json_encode($content, JSON_PRETTY_PRINT) + ); } public function testPrivateEntityFields() @@ -40,7 +43,7 @@ public function testPrivateEntityFields() self::createClient(), '/test/private/', [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'entity/private', 'id' => 'test', 'params' => [ @@ -54,17 +57,26 @@ public function testPrivateEntityFields() self::assertInstanceOf(\stdClass::class, $content); $jsonResponse = new SyncResponse($content); - self::assertTrue($jsonResponse->isSuccessful()); - self::assertTrue(isset($jsonResponse->getBody()->{'sample_payload'})); - self::assertEquals('my-payload', $jsonResponse->getBody()->{'sample_payload'}); - - self::assertTrue( - isset($jsonResponse->getBody()->{'private_payload'}), + self::assertTrue($jsonResponse->isSuccessful(), json_encode($content, JSON_PRETTY_PRINT)); + self::assertTrue(isset($jsonResponse->getBody()->{'sample_payload'}), json_encode($content, JSON_PRETTY_PRINT)); + self::assertEquals( + 'my-payload', + $jsonResponse->getBody()->{'sample_payload'}, json_encode($content, JSON_PRETTY_PRINT) ); - self::assertEquals('secret-payload', $jsonResponse->getBody()->{'private_payload'}); - } + if (static::$kernel->getContainer()->has('jms_serializer')) { + self::assertTrue( + isset($jsonResponse->getBody()->{'private_payload'}), + json_encode($content, JSON_PRETTY_PRINT) + ); + self::assertEquals( + 'secret-payload', + $jsonResponse->getBody()->{'private_payload'}, + json_encode($content, JSON_PRETTY_PRINT) + ); + } + } public function testNestedContext() { @@ -72,7 +84,7 @@ public function testNestedContext() self::createClient(), '/test/private/', [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'entity/nested', 'id' => 'test', 'params' => [ @@ -86,14 +98,24 @@ public function testNestedContext() self::assertInstanceOf(\stdClass::class, $content); $jsonResponse = new SyncResponse($content); - self::assertTrue($jsonResponse->isSuccessful()); - self::assertTrue(isset($jsonResponse->getBody()->{'sample_payload'})); - self::assertEquals('my-payload', $jsonResponse->getBody()->{'sample_payload'}); - - self::assertTrue( - isset($jsonResponse->getBody()->{'private_payload'}), + self::assertTrue($jsonResponse->isSuccessful(), json_encode($content, JSON_PRETTY_PRINT)); + self::assertTrue(isset($jsonResponse->getBody()->{'sample_payload'}), json_encode($content, JSON_PRETTY_PRINT)); + self::assertEquals( + 'my-payload', + $jsonResponse->getBody()->{'sample_payload'}, json_encode($content, JSON_PRETTY_PRINT) ); - self::assertEquals('secret-payload', $jsonResponse->getBody()->{'private_payload'}); + + if (static::$kernel->getContainer()->has('jms_serializer')) { + self::assertTrue( + isset($jsonResponse->getBody()->{'private_payload'}), + json_encode($content, JSON_PRETTY_PRINT) + ); + self::assertEquals( + 'secret-payload', + $jsonResponse->getBody()->{'private_payload'}, + json_encode($content, JSON_PRETTY_PRINT) + ); + } } } diff --git a/Test/Tests/ExceptionHandlingTest.php b/Test/Tests/ExceptionHandlingTest.php index a789512..f64984e 100644 --- a/Test/Tests/ExceptionHandlingTest.php +++ b/Test/Tests/ExceptionHandlingTest.php @@ -2,12 +2,12 @@ namespace Bankiru\Api\JsonRpc\Test\Tests; -use Bankiru\Api\JsonRpc\JsonRpcBundle; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use ScayTrase\Api\JsonRpc\JsonRpcError; use ScayTrase\Api\JsonRpc\JsonRpcResponseInterface; use ScayTrase\Api\JsonRpc\SyncResponse; -class ExceptionHandlingTest extends JsonRpcTestCase +final class ExceptionHandlingTest extends JsonRpcTestCase { public function testBatchWithFailingMethod() { @@ -18,7 +18,7 @@ public function testBatchWithFailingMethod() '/test/', [ [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'sample', 'id' => 'test', 'params' => [ @@ -27,7 +27,7 @@ public function testBatchWithFailingMethod() ], ], [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'exception', 'id' => 'test2', 'params' => [ @@ -37,7 +37,7 @@ public function testBatchWithFailingMethod() ], [ //notification - no id - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'method' => 'notification', 'param' => [ 'notification' => 'message', @@ -68,7 +68,7 @@ public function getInvalidRequests() return [ 'No method' => [ [ - 'jsonrpc' => JsonRpcBundle::VERSION, + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, 'id' => 'test', 'params' => [], ], diff --git a/Test/Tests/Fixtures/Kernel.php b/Test/Tests/Fixtures/Kernel.php deleted file mode 100644 index 53a93f6..0000000 --- a/Test/Tests/Fixtures/Kernel.php +++ /dev/null @@ -1,47 +0,0 @@ -load(__DIR__ . '/config.yml'); - } - - public function getEnvironment() - { - return 'test'; - } -} diff --git a/Test/Tests/Fixtures/config.yml b/Test/Tests/Fixtures/config.yml deleted file mode 100644 index fe38e1e..0000000 --- a/Test/Tests/Fixtures/config.yml +++ /dev/null @@ -1,12 +0,0 @@ -framework: - test: ~ - secret: test - assets: false - router: - resource: "%kernel.root_dir%/routing.yml" - templating: false - translator: ~ - -services: - logger: - class: Psr\Log\NullLogger diff --git a/Test/Tests/JsonRpcTestCase.php b/Test/Tests/JsonRpcTestCase.php index db78b78..400c18e 100644 --- a/Test/Tests/JsonRpcTestCase.php +++ b/Test/Tests/JsonRpcTestCase.php @@ -2,7 +2,11 @@ namespace Bankiru\Api\JsonRpc\Test\Tests; -use Bankiru\Api\JsonRpc\Test\Tests\Fixtures\Kernel; +use Bankiru\Api\BrowserKit\JsonRpcClient; +use Bankiru\Api\JsonRpc\Test\Kernel\TestKernel; +use ScayTrase\Api\IdGenerator\UuidGenerator; +use ScayTrase\Api\JsonRpc\JsonRpcResponseInterface; +use ScayTrase\Api\Rpc\RpcRequestInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpKernel\Client; @@ -10,7 +14,7 @@ abstract class JsonRpcTestCase extends WebTestCase { protected static function getKernelClass() { - return Kernel::class; + return TestKernel::class; } /** @@ -30,4 +34,22 @@ protected function sendRequest(Client $client = null, $endpoint, $requests) return $client->getResponse(); } + + /** + * @param RpcRequestInterface $request + * @param string $endpoint + * @param Client|null $client + * + * @return JsonRpcResponseInterface + */ + protected function sendJsonRpcRequest(RpcRequestInterface $request, $endpoint, Client $client = null) + { + if (null === $client) { + $client = static::createClient(); + } + + $jsonRpcClient = new JsonRpcClient($client, $endpoint, new UuidGenerator()); + + return $jsonRpcClient->invoke($request)->getResponse($request); + } } diff --git a/Test/Tests/RouterTest.php b/Test/Tests/RouterTest.php index f511a86..4c56bab 100644 --- a/Test/Tests/RouterTest.php +++ b/Test/Tests/RouterTest.php @@ -7,7 +7,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Routing\RequestContext; -class RouterTest extends WebTestCase +final class RouterTest extends WebTestCase { public function testHttpRoutes() { @@ -16,18 +16,18 @@ public function testHttpRoutes() $context = new RequestContext(); $context->setMethod('POST'); $router->setContext($context); - $router->match('/test/'); + self::assertNotEmpty($router->match('/test/')); } public function testRpcRouterCollection() { $client = self::createClient(); - foreach (['test', 'test_private'] as $endpoint) /** @var MethodCollection $collection */ { + foreach (['test', 'test_private'] as $endpoint) { /** @var Router $router */ - $router = $client->getContainer()->get('rpc.endpoint_router.' . $endpoint); + $router = $client->getContainer()->get('rpc_server.endpoint_router.' . $endpoint); self::assertNotNull($router); - $collection = $router->getCollection(); + $collection = $router->getMethodCollection(); self::assertNotNull($router); self::assertInstanceOf(MethodCollection::class, $collection); diff --git a/Test/Tests/SampleControllerTest.php b/Test/Tests/SampleControllerTest.php index 54ca6e1..9163f9c 100644 --- a/Test/Tests/SampleControllerTest.php +++ b/Test/Tests/SampleControllerTest.php @@ -2,10 +2,10 @@ namespace Bankiru\Api\JsonRpc\Test\Tests; -use Bankiru\Api\JsonRpc\JsonRpcBundle; +use Bankiru\Api\JsonRpc\BankiruJsonRpcServerBundle; use ScayTrase\Api\JsonRpc\SyncResponse; -class SampleControllerTest extends JsonRpcTestCase +final class SampleControllerTest extends JsonRpcTestCase { public function testBatchRequest() { @@ -14,19 +14,19 @@ public function testBatchRequest() '/test/', [ [ - 'jsonrpc' => JsonRpcBundle::VERSION, - 'method' => 'sample', - 'id' => 'test', - 'params' => [ + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, + 'method' => 'sample', + 'id' => 'test', + 'params' => [ 'param1' => 'value1', 'param2' => 100500, ], ], [ //notification - no id - 'jsonrpc' => JsonRpcBundle::VERSION, - 'method' => 'notification', - 'param' => [ + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, + 'method' => 'notification', + 'param' => [ 'notification' => 'message', ], ], @@ -42,10 +42,10 @@ public function testArrayRequest() self::createClient(), '/test/', [ - 'jsonrpc' => JsonRpcBundle::VERSION, - 'method' => 'array', - 'id' => 'test', - 'params' => [ + 'jsonrpc' => BankiruJsonRpcServerBundle::JSONRPC_VERSION, + 'method' => 'array', + 'id' => 'test', + 'params' => [ 'payload' => 'my-payload', ], ] @@ -57,7 +57,14 @@ public function testArrayRequest() $jsonResponse = new SyncResponse($content); self::assertTrue($jsonResponse->isSuccessful(), json_encode($content)); - self::assertTrue(isset($jsonResponse->getBody()[0]->{'sample_payload'})); - self::assertEquals('my-payload', $jsonResponse->getBody()[0]->{'sample_payload'}); + self::assertTrue( + isset($jsonResponse->getBody()[0]->{'sample_payload'}), + json_encode($content, JSON_PRETTY_PRINT) + ); + self::assertEquals( + 'my-payload', + $jsonResponse->getBody()[0]->{'sample_payload'}, + json_encode($content, JSON_PRETTY_PRINT) + ); } } diff --git a/Test/Tests/SecurityTest.php b/Test/Tests/SecurityTest.php new file mode 100644 index 0000000..3e854e2 --- /dev/null +++ b/Test/Tests/SecurityTest.php @@ -0,0 +1,24 @@ +sendJsonRpcRequest(new Request('prefix/security/private', []), '/test/'); + + self::assertFalse($response->isSuccessful()); + self::assertEquals($response->getError()->getCode(), JsonRpcError::METHOD_NOT_FOUND); + } + + public function testPublicMethodRequest() + { + $response = $this->sendJsonRpcRequest(new Request('prefix/security/public', []), '/test/'); + + self::assertTrue($response->isSuccessful(), json_encode($response, JSON_PRETTY_PRINT)); + } +} diff --git a/Tests/JsonRpcControllerTest.php b/Tests/JsonRpcControllerTest.php new file mode 100644 index 0000000..76a8401 --- /dev/null +++ b/Tests/JsonRpcControllerTest.php @@ -0,0 +1,241 @@ + [null], + 'string' => ['test'], + 'invalid_json' => [substr(json_encode(['test']), 2)], + ]; + } + + /** + * @dataProvider getInvalidJsonRequests + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * + * @param $content + */ + public function testInvalidJsonHandling($content) + { + $controller = $this->createController(); + + $request = $this->createJsonRequest('/', $content); + $controller->jsonRpcAction($request); + } + + public function getInvalidJsonRpcRequests() + { + return [ + 'invalid version' => [['jsonrpc' => '1.0']], + 'no method' => [['jsonrpc' => '2.0']], + 'no version' => [['method' => 'test']], + ]; + } + + /** + * @dataProvider getInvalidJsonRpcRequests + * @expectedException \Bankiru\Api\JsonRpc\Exception\InvalidRequestException + * + * @param $content + */ + public function testInvalidJsonRpcHandling($content) + { + $controller = $this->createController(); + + $request = $this->createJsonRequest('/', $content); + $controller->jsonRpcAction($request); + } + + public function getValidResponseAndRequests() + { + return [ + 'empty batch' => [[], '[]'], + 'single' => [ + [ + 'jsonrpc' => '2.0', + 'id' => 'test', + 'method' => 'test', + ], + '{"jsonrpc":"2.0","id":"test","result":{"success":true}}', + ], + 'batch' => [ + [ + [ + 'jsonrpc' => '2.0', + 'id' => 'test', + 'method' => 'test', + ], + ], + '[{"jsonrpc":"2.0","id":"test","result":{"success":true}}]', + ], + ]; + } + + /** + * @dataProvider getValidResponseAndRequests + * + * @param array $content + * @param string $responseBody + */ + public function testValidRequestHandling($content, $responseBody) + { + $controller = $this->createController(); + $request = $this->createJsonRequest( + '/', + $content + ); + $response = $controller->jsonRpcAction($request); + + self::assertInstanceOf(JsonRpcHttpResponse::class, $response); + self::assertTrue($response->isSuccessful()); + self::assertEquals($responseBody, $response->getContent()); + } + + public function testExceptionHandling() + { + $controller = $this->createController(); + $request = $this->createJsonRequest( + '/', + [ + 'jsonrpc' => '2.0', + 'id' => 'test', + 'method' => 'exception', + ] + ); + $response = $controller->jsonRpcAction($request); + + self::assertTrue($response->isSuccessful()); + self::assertEquals( + '{"jsonrpc":"2.0","id":"test","error":{"code":-32603,"message":"Failure!","data":null}}', + $response->getContent() + ); + } + + public function testExceptionHandlingAmongBatchRequest() + { + $controller = $this->createController(); + $request = $this->createJsonRequest( + '/', + [ + [ + 'jsonrpc' => '2.0', + 'id' => 'test1', + 'method' => 'test', + ], + [ + 'jsonrpc' => '2.0', + 'id' => 'test2', + 'method' => 'exception', + ], + ] + ); + $response = $controller->jsonRpcAction($request); + + self::assertTrue($response->isSuccessful()); + self::assertEquals( + '[{"jsonrpc":"2.0","id":"test1","result":{"success":true}},{"jsonrpc":"2.0","id":"test2","error":{"code":-32603,"message":"Failure!","data":null}}]', + $response->getContent() + ); + } + + /** + * @param string $uri + * @param mixed $content + * + * @return Request + */ + private function createJsonRequest($uri, $content) + { + return Request::create($uri, 'POST', [], [], [], [], json_encode($content)); + } + + private function getContainerMock() + { + $mock = $this->prophesize(ContainerInterface::class); + $kernel = $this->prophesize(KernelInterface::class); + $resolver = $this->prophesize(ControllerResolverInterface::class); + $resolver->getController(Argument::type(RpcRequestInterface::class))->willReturn( + function (JsonRpcRequestInterface $request) { + if ($request->getMethod() === 'exception') { + throw new \LogicException('Failure!'); + } + + return new JsonRpcResponse($request->getId(), (object)['success' => true]); + } + ); + $resolver->getArguments(Argument::type(RpcRequestInterface::class), Argument::any())->will( + function (array $args) { + return [ + $args[0], + ]; + } + ); + + $evm = $this->prophesize(EventDispatcherInterface::class); + $evm->dispatch(Argument::exact(RpcEvents::EXCEPTION), Argument::type(GetExceptionResponseEvent::class)) + ->will( + function ($args) { + /** @var GetExceptionResponseEvent $event */ + $event = $args[1]; + + /** @var JsonRpcRequestInterface $request */ + $request = $event->getRequest(); + + $event->setResponse( + new JsonRpcResponse( + $request->getId(), + null, new JsonRpcError( + JsonRpcError::INTERNAL_ERROR, + $event->getException()->getMessage() + ) + ) + ); + } + ); + $evm->dispatch(Argument::exact(RpcEvents::FINISH_REQUEST), Argument::type(FinishRequestEvent::class)) + ->willReturn(null); + $evm->dispatch(Argument::exact(RpcEvents::CONTROLLER), Argument::type(FilterControllerEvent::class)) + ->willReturn(null); + $evm->dispatch(Argument::exact(RpcEvents::REQUEST), Argument::type(GetResponseEvent::class))->willReturn(null); + $evm->dispatch(Argument::exact(RpcEvents::RESPONSE), Argument::type(FilterResponseEvent::class)) + ->willReturn(null); + + $mock->get(Argument::exact('jsonrpc_server.controller_resolver'))->willReturn($resolver->reveal()); + $mock->get(Argument::exact('event_dispatcher'))->willReturn($evm->reveal()); + $mock->get(Argument::exact('kernel'))->willReturn($kernel->reveal()); + + return $mock->reveal(); + } + + private function createController() + { + $controller = new JsonRpcController(); + $controller->setContainer($this->getContainerMock()); + + return $controller; + } +} diff --git a/Tests/JsonRpcHttpResponseTest.php b/Tests/JsonRpcHttpResponseTest.php new file mode 100644 index 0000000..dad36f4 --- /dev/null +++ b/Tests/JsonRpcHttpResponseTest.php @@ -0,0 +1,43 @@ + true], null); + + $error = new JsonRpcResponse( + 'single', + null, + new JsonRpcError(JsonRpcError::METHOD_NOT_FOUND, 'Invalid method') + ); + + return [ + 'empty' => [null], + 'empty array' => [[]], + 'single success' => [clone $success], + 'single failure' => [clone $error], + 'mixed array' => [[clone $success, clone $error]], + ]; + } + + /** + * @dataProvider getResponseVariants + * + * @param $responses + */ + public function testResponseProvidesCorrectHeaders($responses) + { + $response = new JsonRpcHttpResponse($responses); + self::assertTrue($response->isSuccessful()); + self::assertTrue($response->headers->has('Content-Type')); + self::assertEquals('application/json', $response->headers->get('Content-Type')); + } +} diff --git a/bootstrap.php b/bootstrap.php index 8c57a77..445fa3b 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -1,12 +1,12 @@ dump(new Configuration()); + + diff --git a/phpunit.xml b/phpunit.xml index 3b8fa98..31ec208 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,17 +9,21 @@ > - - + ./Test/Tests/ + + ./Tests/ + + Test/ + Tests/ vendor/ build/