diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a6ac9a5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+vendor
+build
+#A library must not provide a composer.lock file
+composer.lock
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
new file mode 100644
index 0000000..5a1a311
--- /dev/null
+++ b/.scrutinizer.yml
@@ -0,0 +1,92 @@
+build_failure_conditions:
+ - 'project.metric_change("scrutinizer.quality", < -0.30)'
+ - 'elements.rating(<= D).exists' # No classes/methods with a rating of D or worse
+ - 'issues.severity(>= MAJOR).exists' # New major or higher severity issues
+ - 'project.metric("scrutinizer.quality", < 9)' # Code Quality Rating drops below 9
+ - 'project.metric("scrutinizer.test_coverage", < 1)' # Code Coverage must alway be 100%
+ - 'patches.label("Doc Comments").exists' # No doc comments patches allowed
+ - 'patches.label("Spacing").exists' # No spacing patches allowed
+ - 'patches.label("Bug").exists' # No bug patches allowed
+ - 'issues.label("coding-style").exists' # No coding style issues allowed
+build:
+ dependencies:
+ override:
+ - make build
+ tests:
+ stop_on_failure: true
+ override:
+ - php-scrutinizer-run --enable-security-analysis
+ -
+ command: make codestyle
+ analysis:
+ file: 'build/reports/cs-data'
+ format: 'php-cs-checkstyle'
+ -
+ command: make coverage
+ idle_timeout: 1200
+ coverage:
+ file: 'build/coverage/clover.xml'
+ format: 'php-clover'
+ cache:
+ directories:
+ - ~/.composer
+ - vendor
+
+ environment:
+ variables:
+ CI: 'true'
+ TEST_OUTPUT_STYLE: 'pretty'
+ COMPOSER_OPTIONS: '--optimize-autoloader'
+ COVERAGE_OUTPUT_STYLE: 'clover'
+ COVERAGE_CLOVER_FILE_PATH: 'build/coverage/clover.xml'
+ PHPCS_REPORT_STYLE: 'checkstyle'
+ PHPCS_REPORT_FILE: 'build/reports/cs-data'
+ php:
+ version: "7.1"
+ timezone: UTC
+ postgresql: false
+ redis: false
+filter:
+ paths:
+ - src/*
+checks:
+ php:
+ code_rating: true
+ duplication: true
+ no_debug_code: true
+ check_method_contracts:
+ verify_interface_like_constraints: true
+ verify_documented_constraints: true
+ verify_parent_constraints: true
+ simplify_boolean_return: true
+ return_doc_comments: true
+ return_doc_comment_if_not_inferrable: true
+ remove_extra_empty_lines: true
+ properties_in_camelcaps: true
+ phpunit_assertions: true
+ parameters_in_camelcaps: true
+ parameter_doc_comments: true
+ param_doc_comment_if_not_inferrable: true
+ overriding_parameter: true
+ no_trailing_whitespace: true
+ no_short_variable_names:
+ minimum: '3'
+ no_short_method_names:
+ minimum: '3'
+ no_long_variable_names:
+ maximum: '20'
+ no_goto: true
+ naming_conventions:
+ local_variable: '^[a-z][a-zA-Z0-9]*$'
+ abstract_class_name: ^Abstract|Factory$
+ utility_class_name: 'Utils?$'
+ constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$'
+ property_name: '^[a-z][a-zA-Z0-9]*$'
+ method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$'
+ parameter_name: '^[a-z][a-zA-Z0-9]*$'
+ interface_name: '^[A-Z][a-zA-Z0-9]*Interface$'
+ type_name: '^[A-Z][a-zA-Z0-9]*$'
+ exception_name: '^[A-Z][a-zA-Z0-9]*Exception$'
+ isser_method_name: '^(?:is|has|should|may|supports)'
+ more_specific_types_in_doc_comments: true
+ fix_doc_comments: false
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b9cd43f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,33 @@
+language: php
+
+php:
+ - '7.0'
+ - '7.1'
+ - '7.2'
+
+env:
+ global:
+ CI: 'true'
+ TEST_OUTPUT_STYLE: 'pretty'
+ PHPCS_REPORT_STYLE: 'full'
+ COMPOSER_OPTIONS: '--optimize-autoloader'
+
+sudo: false
+
+matrix:
+ fast_finish: true
+
+before_install:
+ # remove xdebug to speed up build
+ - phpenv config-rm xdebug.ini
+
+install:
+ - make build
+script:
+ - make test-technical
+ - make test-functional
+
+cache:
+ directories:
+ - $HOME/.composer
+ - vendor
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..5741f51
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# Contributing
+
+## Getting Started
+ * Fork, then clone the repo:
+```bash
+git clone git@github.com:your-username/symfony-jsonrpc-http-server-openapi-doc.git
+````
+
+ * Make sure everything goes well:
+```bash
+make build
+make test
+```
+
+ * Make your changes (Add/Update tests according to your changes).
+ * Make sure tests are still green:
+```bash
+make test
+```
+
+ * To check code coverage, launch
+```bash
+make coverage
+```
+
+ * Push to your fork and [submit a pull request](https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc/compare/).
+ * Wait for feedback or merge.
+
+ Some stuff that will increase your pull request's acceptance:
+ * Write tests.
+ * Follow PSR-2 coding style.
+ * Write good commit messages.
+ * Do not rebase or squash your commits when a review has been made.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..61a3638
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,95 @@
+COLOR_ENABLED ?= true
+TEST_OUTPUT_STYLE ?= dot
+COVERAGE_OUTPUT_STYLE ?= html
+
+## DIRECTORY AND FILE
+BUILD_DIRECTORY ?= build
+REPORTS_DIRECTORY ?= ${BUILD_DIRECTORY}/reports
+COVERAGE_DIRECTORY ?= ${BUILD_DIRECTORY}/coverage
+COVERAGE_CLOVER_FILE_PATH ?= ${COVERAGE_DIRECTORY}/clover.xml
+
+## Commands options
+### Composer
+#COMPOSER_OPTIONS=
+### Phpcs
+PHPCS_REPORT_STYLE ?= full
+#PHPCS_REPORT_FILE=
+#PHPCS_REPORT_FILE_OPTION=
+
+# Enable/Disable color ouput
+ifeq ("${COLOR_ENABLED}","true")
+ PHPUNIT_COLOR_OPTION ?= --colors=always
+ BEHAT_COLOR_OPTION ?= --colors
+ PHPCS_COLOR_OPTION ?= --colors
+ COMPOSER_COLOR_OPTION ?= --ansi
+else
+ PHPUNIT_COLOR_OPTION ?= --colors=never
+ PHPCS_COLOR_OPTION ?= --no-colors
+ BEHAT_COLOR_OPTION ?= --no-colors
+ COMPOSER_COLOR_OPTION ?= --no-ansi
+endif
+
+ifeq ("${TEST_OUTPUT_STYLE}","pretty")
+ PHPUNIT_OUTPUT_STYLE_OPTION ?= --testdox
+ BEHAT_OUTPUT_STYLE_OPTION ?= --format pretty
+else
+ PHPUNIT_OUTPUT_STYLE_OPTION ?=
+ BEHAT_OUTPUT_STYLE_OPTION ?= --format progress
+endif
+
+ifeq ("${COVERAGE_OUTPUT_STYLE}","clover")
+ PHPUNIT_COVERAGE_OPTION ?= --coverage-clover ${COVERAGE_CLOVER_FILE_PATH}
+else
+ ifeq ("${COVERAGE_OUTPUT_STYLE}","html")
+ PHPUNIT_COVERAGE_OPTION ?= --coverage-html ${COVERAGE_DIRECTORY}
+ else
+ PHPUNIT_COVERAGE_OPTION ?= --coverage-text
+ endif
+endif
+
+ifneq ("${PHPCS_REPORT_FILE}","")
+ PHPCS_REPORT_FILE_OPTION ?= --report-file=${PHPCS_REPORT_FILE}
+endif
+
+
+## Project build (install and configure)
+build: install configure
+
+## Project installation
+install:
+ composer install ${COMPOSER_COLOR_OPTION} ${COMPOSER_OPTIONS} --prefer-dist --no-suggest --no-interaction
+
+## project Configuration
+configure:
+
+# Project tests
+test:
+ make test-functional
+ make test-technical
+ make codestyle
+
+test-technical:
+ ./vendor/bin/phpunit ${PHPUNIT_COLOR_OPTION} ${PHPUNIT_OUTPUT_STYLE_OPTION} --testsuite technical
+
+test-functional:
+ ./vendor/bin/phpunit ${PHPUNIT_COLOR_OPTION} ${PHPUNIT_OUTPUT_STYLE_OPTION} --testsuite functional
+ ./vendor/bin/behat ${BEHAT_COLOR_OPTION} ${BEHAT_OUTPUT_STYLE_OPTION} --no-snippets
+
+codestyle: create-reports-directory
+ ./vendor/bin/phpcs --standard=phpcs.xml.dist ${PHPCS_COLOR_OPTION} ${PHPCS_REPORT_FILE_OPTION} --report=${PHPCS_REPORT_STYLE}
+
+coverage: create-coverage-directory
+ ./vendor/bin/phpunit ${PHPUNIT_COLOR_OPTION} ${PHPUNIT_OUTPUT_STYLE_OPTION} ${PHPUNIT_COVERAGE_OPTION}
+
+
+
+# Internal commands
+create-coverage-directory:
+ mkdir -p ${COVERAGE_DIRECTORY}
+
+create-reports-directory:
+ mkdir -p ${REPORTS_DIRECTORY}
+
+
+.PHONY: build install configure test test-technical test-functional codestyle coverage create-coverage-directory create-reports-directory
+.DEFAULT: build
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..482af6d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# Symfony JSON-RPC Http server OpenAPI documentation
+
+[](https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc) [](https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc) [](https://php.net/)
+
+[](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/?branch=master) [](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/build-status/master) [](https://scrutinizer-ci.com/g/yoanm/symfony-jsonrpc-http-server-openapi-doc/?branch=master)
+
+[](https://travis-ci.org/yoanm/symfony-jsonrpc-http-server-openapi-doc) [](https://travis-ci.org/yoanm/symfony-jsonrpc-http-server-openapi-doc)
+
+[](https://packagist.org/packages/yoanm/symfony-jsonrpc-http-server-openapi-doc) [](https://packagist.org/packages/yoanm/symfony-jsonrpc-http-server-openapi-doc)
+
+Symfony bundle for easy JSON-RPC server OpenAPI 3.0.0 documentation
+
+## How to use
+
+
+## Contributing
+See [contributing note](./CONTRIBUTING.md)
diff --git a/behat.yml b/behat.yml
new file mode 100644
index 0000000..a5ce152
--- /dev/null
+++ b/behat.yml
@@ -0,0 +1,5 @@
+default:
+ suites:
+ default:
+ contexts:
+ - Tests\Functional\BehatContext\FeatureContext: ~
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..69beefd
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,46 @@
+{
+ "name": "yoanm/symfony-jsonrpc-http-server-openapi-doc",
+ "description": "Symfony bundle for easy JSON-RPC server OpenAPI 3.0.0 documentation",
+ "license": "MIT",
+ "type": "library",
+ "support": {
+ "issues": "https://github.com/yoanm/symfony-jsonrpc-http-server-openapi-doc/issues"
+ },
+ "authors": [
+ {
+ "name": "Yoanm",
+ "email": "yoanm@users.noreply.github.com",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Yoanm\\SymfonyJsonRpcHttpServerOpenAPIDoc\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests",
+ "Tests\\Functional\\BehatContext\\": "features/bootstrap"
+ }
+ },
+ "require": {
+ "php": ">=5.5",
+ "yoanm/jsonrpc-server-doc-sdk": "dev-feature/improve",
+ "yoanm/jsonrpc-http-server-openapi-doc-sdk": "dev-feature/improve",
+ "yoanm/symfony-jsonrpc-http-server-doc": "dev-feature/improve",
+ "symfony/http-foundation": "^3.0 || ^4.0",
+ "symfony/http-kernel": "^3.0 || ^4.0",
+ "symfony/config": "^3.0 || ^4.0",
+ "symfony/dependency-injection": "^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "behat/behat": "~3.0",
+ "squizlabs/php_codesniffer": "3.*",
+ "phpunit/phpunit": "^6.0 || ^7.0",
+ "matthiasnoback/symfony-dependency-injection-test": "^2.0 || ^3.0",
+ "matthiasnoback/symfony-config-test": "^3.0 || ^4.0",
+ "symfony/framework-bundle": "^3.4",
+ "yoanm/php-unit-extended": "~1.0"
+ }
+}
diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php
new file mode 100644
index 0000000..f562608
--- /dev/null
+++ b/features/bootstrap/FeatureContext.php
@@ -0,0 +1,21 @@
+
+
+ src
+ tests
+ features/bootstrap
+
+
+
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..cb7c344
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+ tests/Functional
+
+
+ tests/Technical
+
+
+
+
+
+ src
+
+
+
diff --git a/src/.gitkeep b/src/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/DependencyInjection/JsonRpcHttpServerOpenAPIDocExtension.php b/src/DependencyInjection/JsonRpcHttpServerOpenAPIDocExtension.php
new file mode 100644
index 0000000..53a0748
--- /dev/null
+++ b/src/DependencyInjection/JsonRpcHttpServerOpenAPIDocExtension.php
@@ -0,0 +1,53 @@
+load('services.sdk.yaml');
+ $loader->load('services.public.yaml');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNamespace()
+ {
+ return 'http://example.org/schema/dic/'.$this->getAlias();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getXsdValidationBasePath()
+ {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAlias()
+ {
+ return self::EXTENSION_IDENTIFIER;
+ }
+}
diff --git a/src/Event/OpenAPIDocCreatedEvent.php b/src/Event/OpenAPIDocCreatedEvent.php
new file mode 100644
index 0000000..1b9ac3e
--- /dev/null
+++ b/src/Event/OpenAPIDocCreatedEvent.php
@@ -0,0 +1,56 @@
+openAPIDoc = $openAPIDoc;
+ $this->serverDoc = $serverDoc;
+ }
+
+ /**
+ * @return HttpServerDoc
+ */
+ public function getOpenAPIDoc()
+ {
+ return $this->openAPIDoc;
+ }
+
+ /**
+ * @return HttpServerDoc|null
+ */
+ public function getServerDoc()
+ {
+ return $this->serverDoc;
+ }
+
+ /**
+ * @param array $openAPIDoc
+ *
+ * @return OpenAPIDocCreatedEvent
+ */
+ public function setOpenAPIDoc(array $openAPIDoc)
+ {
+ $this->openAPIDoc = $openAPIDoc;
+
+ return $this;
+ }
+}
diff --git a/src/JsonRpcHttpServerOpenAPIDocBundle.php b/src/JsonRpcHttpServerOpenAPIDocBundle.php
new file mode 100644
index 0000000..a4cf7ed
--- /dev/null
+++ b/src/JsonRpcHttpServerOpenAPIDocBundle.php
@@ -0,0 +1,11 @@
+dispatcher = $dispatcher;
+ $this->HttpServerDocCreator = $HttpServerDocCreator;
+ $this->docNormalizer = $docNormalizer;
+ }
+
+ /**
+ * @param string|null $host
+ *
+ * @return array
+ */
+ public function getDoc($host = null)
+ {
+ $rawDoc = $this->HttpServerDocCreator->create($host);
+
+ $openApiDoc = $this->docNormalizer->normalize($rawDoc);
+
+ $event = new OpenAPIDocCreatedEvent($openApiDoc, $rawDoc);
+ $this->dispatcher->dispatch($event::EVENT_NAME, $event);
+
+ return $event->getOpenAPIDoc();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($filename, $host = null)
+ {
+ return 'openapi.json' === $filename;
+ }
+}
diff --git a/src/Resources/config/services.public.yaml b/src/Resources/config/services.public.yaml
new file mode 100644
index 0000000..d9f186d
--- /dev/null
+++ b/src/Resources/config/services.public.yaml
@@ -0,0 +1,12 @@
+services:
+ _defaults:
+ public: true
+
+ json_rpc_http_server_open_api_doc.provider:
+ class: Yoanm\SymfonyJsonRpcHttpServerOpenAPIDoc\Provider\DocProvider
+ arguments:
+ - '@event_dispatcher'
+ - '@json_rpc_http_server_doc.creator.http_server'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.doc'
+ tags: ['json_rpc_server_doc.doc_provider']
+
diff --git a/src/Resources/config/services.sdk.yaml b/src/Resources/config/services.sdk.yaml
new file mode 100644
index 0000000..e0280c8
--- /dev/null
+++ b/src/Resources/config/services.sdk.yaml
@@ -0,0 +1,58 @@
+services:
+ _defaults:
+ public: true
+
+
+ # Def resolver
+ json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Resolver\DefinitionRefResolver
+
+
+ # Normalizer
+ json_rpc_http_server_open_api_doc_sdk.normalizer.doc:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\DocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.request_doc'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.response'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.external_schema_list'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.operation'
+
+ ## Components
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.schema_type:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\SchemaTypeNormalizer
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.parameter_doc:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\TypeDocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.schema_type'
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ShapeNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref'
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.error:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ErrorDocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.parameter_doc'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape'
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.external_schema_list:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ExternalSchemaListDocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.parameter_doc'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.error'
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.request_doc:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\RequestDocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape'
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.response:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\ResponseDocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.shape'
+ json_rpc_http_server_open_api_doc_sdk.normalizer.component.operation:
+ class: Yoanm\JsonRpcHttpServerOpenAPIDoc\Normalizer\Component\OperationDocNormalizer
+ arguments:
+ - '@json_rpc_http_server_open_api_doc_sdk.resolver.definition_ref'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.request_doc'
+ - '@json_rpc_http_server_open_api_doc_sdk.normalizer.component.response'
diff --git a/tests/Common/DependencyInjection/AbstractTestClass.php b/tests/Common/DependencyInjection/AbstractTestClass.php
new file mode 100644
index 0000000..da704f4
--- /dev/null
+++ b/tests/Common/DependencyInjection/AbstractTestClass.php
@@ -0,0 +1,91 @@
+compile();
+ }
+
+
+ protected function assertEndpointIsUsable()
+ {
+ // Retrieving this service will imply to load all related dependencies
+ // Any binding issues will be raised
+ $this->assertNotNull($this->container->get(self::EXPECTED_ENDPOINT_SERVICE_ID));
+ }
+
+ /**
+ * @param $jsonRpcMethodServiceId
+ */
+ protected function assertJsonRpcMethodServiceIsAvailable($jsonRpcMethodServiceId)
+ {
+ $this->assertNotNull($this->container->get($jsonRpcMethodServiceId));
+ }
+
+ /**
+ * @return Definition
+ */
+ protected function createJsonRpcMethodDefinition()
+ {
+ return (new Definition(\stdClass::class))
+ ->setPrivate(false);
+ }
+
+ /**
+ * @param Definition $definition
+ * @param string $methodName
+ */
+ protected function addJsonRpcMethodTag(Definition $definition, $methodName)
+ {
+ $definition->addTag(
+ self::EXPECTED_JSONRPC_METHOD_TAG,
+ [self::EXPECTED_JSONRPC_METHOD_TAG_METHOD_NAME_KEY => $methodName]
+ );
+ }
+
+ /**
+ * @return Definition
+ */
+ protected function createCustomMethodResolverDefinition()
+ {
+ $customResolverService = new Definition($this->prophesize(MethodResolverInterface::class)->reveal());
+ $customResolverService->addTag(self::EXPECTED_METHOD_RESOLVER_TAG);
+
+ return $customResolverService;
+ }
+}
diff --git a/tests/Functional/.gitkeep b/tests/Functional/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionTest.php b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionTest.php
new file mode 100644
index 0000000..83b79df
--- /dev/null
+++ b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionTest.php
@@ -0,0 +1,220 @@
+load();
+
+ $this->assertContainerBuilderHasService(self::EXPECTED_ENDPOINT_SERVICE_ID, JsonRpcHttpEndpoint::class);
+
+ // Check that service is accessible through the container
+ $this->assertNotNull($this->container->get(self::EXPECTED_ENDPOINT_SERVICE_ID));
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldReturnAnXsdValidationBasePath()
+ {
+ $this->assertNotNull((new JsonRpcHttpServerExtension())->getXsdValidationBasePath());
+ }
+
+ public function testShouldExposeServiceNameResolverService()
+ {
+ $this->load();
+
+ $this->assertContainerBuilderHasService(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ ServiceNameResolver::class
+ );
+
+ // Check that service is accessible through the container
+ $this->assertNotNull($this->container->get(self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID));
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldAliasPSR11MethodResolverByDefault()
+ {
+ $this->load();
+
+ // Assert that MethodManager have the stub resolver
+ $this->assertContainerBuilderHasServiceDefinitionWithArgument(
+ self::EXPECTED_METHOD_MANAGER_SERVICE_ID,
+ 0,
+ new Reference(self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID)
+ );
+
+ // Assert PSR-11 resolver is an alias of the stub
+ $this->assertContainerBuilderHasAlias(
+ self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID,
+ 'json_rpc_http_server.psr11.infra.resolver.method'
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldAliasMethodResolverInjectionFoundByTag()
+ {
+ $myCustomResolverServiceId = 'my_custom_resolver';
+
+ $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition());
+
+ $this->load();
+
+ // Assert that MethodManager have the stub resolver
+ $this->assertContainerBuilderHasServiceDefinitionWithArgument(
+ self::EXPECTED_METHOD_MANAGER_SERVICE_ID,
+ 0,
+ new Reference(self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID)
+ );
+
+ // Assert custom resolver is an alias of the stub
+ $this->assertContainerBuilderHasAlias(
+ self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID,
+ $myCustomResolverServiceId
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testHandleManageJsonRpcMethodTag()
+ {
+ $jsonRpcMethodServiceId = uniqid();
+ $jsonRpcMethodServiceId2 = uniqid();
+ $methodName = 'my-method-name';
+ $methodName2 = 'my-method-name-2';
+
+ // A first method
+ $methodService = $this->createJsonRpcMethodDefinition();
+ $this->addJsonRpcMethodTag($methodService, $methodName);
+ $this->setDefinition($jsonRpcMethodServiceId, $methodService);
+ // A second method
+ $methodService2 = $this->createJsonRpcMethodDefinition();
+ $this->addJsonRpcMethodTag($methodService2, $methodName2);
+ $this->setDefinition($jsonRpcMethodServiceId2, $methodService2);
+
+ $this->load();
+
+ // Assert that method mapping have been correctly injected
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [
+ $methodName,
+ $jsonRpcMethodServiceId
+ ],
+ 0
+ );
+ // Assert that method mapping have been correctly injected
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [
+ $methodName2,
+ $jsonRpcMethodServiceId2
+ ],
+ 1
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testHandleNotManageJsonRpcMethodTagIfCustomResolverIsUsed()
+ {
+ $jsonRpcMethodServiceId = uniqid();
+ $jsonRpcMethodServiceId2 = uniqid();
+ $methodName = 'my-method-name';
+ $methodName2 = 'my-method-name-2';
+
+ // A first method
+ $methodService = $this->createJsonRpcMethodDefinition();
+ $this->addJsonRpcMethodTag($methodService, $methodName);
+ $this->setDefinition($jsonRpcMethodServiceId, $methodService);
+ // A second method
+ $methodService2 = $this->createJsonRpcMethodDefinition();
+ $this->addJsonRpcMethodTag($methodService2, $methodName2);
+ $this->setDefinition($jsonRpcMethodServiceId2, $methodService2);
+
+ // Add the custom method resolver
+ $this->setDefinition(uniqid(), $this->createCustomMethodResolverDefinition());
+
+ $this->load();
+
+ // Assert that no method mapping have been added
+ $this->assertEmpty(
+ $this->container->getDefinition(self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID)->getMethodCalls()
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldThrowAnExceptionIfJsonRpcMethodUsedWithTagIsDoesNotHaveTheMethodTagAttribute()
+ {
+ $jsonRpcMethodServiceId = uniqid();
+ $jsonRpcMethodServiceId2 = uniqid();
+ $methodName = 'my-method-name';
+
+ // A first method
+ $methodService = $this->createJsonRpcMethodDefinition();
+ $this->addJsonRpcMethodTag($methodService, $methodName);
+ $this->setDefinition($jsonRpcMethodServiceId, $methodService);
+ // A second method with empty tag attribute
+ $methodService2 = $this->createJsonRpcMethodDefinition();
+ $methodService2->addTag(self::EXPECTED_JSONRPC_METHOD_TAG);
+ $this->setDefinition($jsonRpcMethodServiceId2, $methodService2);
+
+ $this->expectException(LogicException::class);
+ // Check that exception is for the second method
+ $this->expectExceptionMessage(
+ sprintf(
+ 'Service "%s" is taggued as JSON-RPC method but does not have'
+ . ' method name defined under "%s" tag attribute key',
+ $jsonRpcMethodServiceId2,
+ self::EXPECTED_JSONRPC_METHOD_TAG_METHOD_NAME_KEY
+ )
+ );
+
+ $this->load();
+ }
+
+ public function testShouldThrowAnExceptionIfJsonRpcMethodUsedWithTagIsNotPublic()
+ {
+ $jsonRpcMethodServiceId = uniqid();
+ $jsonRpcMethodServiceId2 = uniqid();
+ $methodName = 'my-method-name';
+ $methodName2 = 'my-method-name-2';
+
+ // A first method
+ $methodService = $this->createJsonRpcMethodDefinition();
+ $this->addJsonRpcMethodTag($methodService, $methodName);
+ $this->setDefinition($jsonRpcMethodServiceId, $methodService);
+ // A second method with private service
+ $methodService2 = $this->createJsonRpcMethodDefinition()->setPublic(false);
+ $this->addJsonRpcMethodTag($methodService2, $methodName2);
+ $this->setDefinition($jsonRpcMethodServiceId2, $methodService2);
+
+ $this->expectException(LogicException::class);
+ // Check that exception is for the second method
+ $this->expectExceptionMessage(
+ sprintf(
+ 'Service "%s" is taggued as JSON-RPC method but is not public. '
+ .'Service must be public in order to retrieve it later',
+ $jsonRpcMethodServiceId2
+ )
+ );
+
+ $this->load();
+ }
+}
diff --git a/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionWithConfigParsedTest.php b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionWithConfigParsedTest.php
new file mode 100644
index 0000000..af608b1
--- /dev/null
+++ b/tests/Functional/DependencyInjection/JsonRpcHttpServerExtensionWithConfigParsedTest.php
@@ -0,0 +1,146 @@
+load(['http_endpoint_path' => $myCustomEndpoint]);
+
+ // Assert custom resolver is an alias of the stub
+ $this->assertContainerBuilderHasParameter(self::EXPECTED_HTTP_ENDPOINT_PATH_CONTAINER_PARAM, $myCustomEndpoint);
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldManageCustomResolverFromConfiguration()
+ {
+ $myCustomResolverServiceId = 'my-custom-resolver';
+ $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition());
+
+ $this->load(['method_resolver' => $myCustomResolverServiceId]);
+
+ // Assert custom resolver is an alias of the stub
+ $this->assertContainerBuilderHasAlias(
+ self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID,
+ $myCustomResolverServiceId
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldManageMethodsMapping()
+ {
+ $serviceA = uniqid();
+ $serviceB = uniqid();
+ $serviceC = uniqid();
+ $methodAName = 'method-a';
+ $methodAAlias1 = 'method-a-alias-1';
+ $methodAAlias2 = 'method-a-alias-2';
+ $methodBName = 'method-b';
+ $methodBAlias = 'method-b-alias';
+ $methodCName = 'method-c';
+ $this->setDefinition($serviceA, $this->createJsonRpcMethodDefinition());
+ $this->setDefinition($serviceB, $this->createJsonRpcMethodDefinition());
+ $this->setDefinition($serviceC, $this->createJsonRpcMethodDefinition());
+
+ $this->load([
+ 'methods_mapping' => [
+ $methodAName => [
+ 'service' => $serviceA,
+ 'aliases' => [$methodAAlias1, $methodAAlias2]
+ ],
+ $methodBName => [
+ 'service' => $serviceB,
+ 'aliases' => $methodBAlias,
+ ],
+ $methodCName => $serviceC
+ ]
+ ]);
+
+ // Assert each methods have been mapped correctly
+
+ // MethodA and aliases
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [$methodAName, $serviceA]
+ );
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [$methodAAlias1, $serviceA]
+ );
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [$methodAAlias2, $serviceA]
+ );
+ // MethodB and alias
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [$methodBName, $serviceB]
+ );
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [$methodBAlias, $serviceB]
+ );
+ // MethodC
+ $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ self::EXPECTED_SERVICE_NAME_RESOLVER_SERVICE_ID,
+ 'addMethodMapping',
+ [$methodCName, $serviceC]
+ );
+
+ $this->assertJsonRpcMethodServiceIsAvailable($serviceA);
+ $this->assertJsonRpcMethodServiceIsAvailable($serviceB);
+ $this->assertJsonRpcMethodServiceIsAvailable($serviceC);
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldThrowAnExceptionIfJsonRpcMethodUsedWithTagIsNotPublic()
+ {
+ $jsonRpcMethodServiceId = uniqid();
+
+ $methodService = $this->createJsonRpcMethodDefinition()->setPublic(false);
+ $this->setDefinition($jsonRpcMethodServiceId, $methodService);
+
+
+ $this->expectException(LogicException::class);
+ // Check that exception is for the second method
+ $this->expectExceptionMessage(
+ sprintf(
+ 'Service "%s" is taggued as JSON-RPC method but is not public. '
+ .'Service must be public in order to retrieve it later',
+ $jsonRpcMethodServiceId
+ )
+ );
+
+ $this->load([
+ 'methods_mapping' => [
+ 'a-method' => $jsonRpcMethodServiceId
+ ]
+ ]);
+ }
+}
diff --git a/tests/Functional/Endpoint/JsonRpcHttpEndpointTest.php b/tests/Functional/Endpoint/JsonRpcHttpEndpointTest.php
new file mode 100644
index 0000000..befdbca
--- /dev/null
+++ b/tests/Functional/Endpoint/JsonRpcHttpEndpointTest.php
@@ -0,0 +1,72 @@
+sdkEndpoint = $this->prophesize(SdkJsonRpcEndpoint::class);
+
+ $this->endpoint = new JsonRpcHttpEndpoint(
+ $this->sdkEndpoint->reveal()
+ );
+ }
+
+ public function testShouldHandleRequestContentAndReturnA200ResponseContainingSDKEndpointReturnedValue()
+ {
+ $requestContent = 'request-content';
+ $expextedResponseContent = 'expected-response-content';
+
+ /** @var Request|ObjectProphecy $request */
+ $request = $this->prophesize(Request::class);
+
+ $request->getMethod()
+ ->willReturn(Request::METHOD_POST)
+ ->shouldBeCalled();
+
+ $request->getContent()
+ ->willReturn($requestContent)
+ ->shouldBeCalled();
+
+ $this->sdkEndpoint->index($requestContent)
+ ->willReturn($expextedResponseContent)
+ ->shouldBeCalled();
+
+ $response = $this->endpoint->index($request->reveal());
+
+ $this->assertSame(Response::HTTP_OK, $response->getStatusCode());
+ $this->assertSame($expextedResponseContent, $response->getContent());
+ }
+
+ public function testShouldCheckIfRequestUsePostMethodAndReturnErrorResponseIfNot()
+ {
+ /** @var Request|ObjectProphecy $request */
+ $request = $this->prophesize(Request::class);
+
+ $request->getMethod()
+ ->willReturn(Request::METHOD_GET)
+ ->shouldBeCalled();
+
+ $response = $this->endpoint->index($request->reveal());
+
+ $this->assertSame(Response::HTTP_METHOD_NOT_ALLOWED, $response->getStatusCode());
+ $this->assertSame('A JSON-RPC HTTP call must use POST', $response->getContent());
+ }
+}
diff --git a/tests/Functional/JsonRpcHttpServerBundleTest.php b/tests/Functional/JsonRpcHttpServerBundleTest.php
new file mode 100644
index 0000000..007793e
--- /dev/null
+++ b/tests/Functional/JsonRpcHttpServerBundleTest.php
@@ -0,0 +1,37 @@
+getContainerExtension()
+ ];
+ }
+
+ public function testShouldManageConfigurationByDefault()
+ {
+ $myCustomResolverServiceId = 'my-custom-resolver';
+ $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition());
+
+ $this->load(['method_resolver' => $myCustomResolverServiceId]);
+
+ // Assert custom resolver is an alias of the stub
+ $this->assertContainerBuilderHasAlias(
+ self::EXPECTED_METHOD_RESOLVER_STUB_SERVICE_ID,
+ $myCustomResolverServiceId
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+}
diff --git a/tests/Functional/Resolver/ServiceNameResolverTest.php b/tests/Functional/Resolver/ServiceNameResolverTest.php
new file mode 100644
index 0000000..ddc77f3
--- /dev/null
+++ b/tests/Functional/Resolver/ServiceNameResolverTest.php
@@ -0,0 +1,37 @@
+resolver = new ServiceNameResolver();
+ }
+
+ public function testShouldSaveMappingAndGiveItBack()
+ {
+ $this->resolver->addMethodMapping('a', 'b');
+ $this->resolver->addMethodMapping('c', 'D');
+ $this->resolver->addMethodMapping('e', 'blabla');
+
+ $this->assertSame('b', $this->resolver->resolve('a'));
+ $this->assertSame('D', $this->resolver->resolve('c'));
+ $this->assertSame('blabla', $this->resolver->resolve('e'));
+ }
+
+ public function testShouldReturnOriginalValueInCaseNoMappingHaveBeenDefinedForAMethod()
+ {
+ $this->resolver->addMethodMapping('a', 'b');
+
+ $this->assertSame('abcde', $this->resolver->resolve('abcde'));
+ }
+}
diff --git a/tests/Technical/.gitkeep b/tests/Technical/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/Technical/DependencyInjection/ConfigurationTest.php b/tests/Technical/DependencyInjection/ConfigurationTest.php
new file mode 100644
index 0000000..2028ba4
--- /dev/null
+++ b/tests/Technical/DependencyInjection/ConfigurationTest.php
@@ -0,0 +1,125 @@
+assertProcessedConfigurationEquals([[]], ['method_resolver'=> false], 'method_resolver');
+ }
+
+ public function testShouldReturnCustomMethodResolverServiceIfDefined()
+ {
+ $expectedCustomMethodResolverService = 'my-service';
+
+ $this->assertProcessedConfigurationEquals(
+ [
+ [
+ 'method_resolver' => $expectedCustomMethodResolverService
+ ]
+ ],
+ [
+ 'method_resolver'=> $expectedCustomMethodResolverService
+ ],
+ 'method_resolver'
+ );
+ }
+
+ public function testShouldManageSimpleMethodMapping()
+ {
+ $expectedMethodName = 'my-method-name';
+ $expectedService = 'my-service-id';
+
+ $this->assertProcessedConfigurationEquals(
+ [
+ [
+ 'methods_mapping' => [
+ $expectedMethodName => $expectedService
+ ]
+ ]
+ ],
+ [
+ 'methods_mapping'=> [
+ $expectedMethodName => [
+ 'service' => $expectedService,
+ 'aliases' => []
+ ]
+ ]
+ ],
+ 'methods_mapping'
+ );
+ }
+
+ public function testShouldManageFullyConfiguredMapping()
+ {
+ $expectedMethodName = 'my-method-name';
+ $expectedAlias1 = 'my-method-name-alias-1';
+ $expectedAlias2 = 'my-method-name-alias-2';
+ $expectedService = 'my-service-id';
+
+ $this->assertProcessedConfigurationEquals(
+ [
+ [
+ 'methods_mapping' => [
+ $expectedMethodName => [
+ 'service' => $expectedService,
+ 'aliases' => [$expectedAlias1, $expectedAlias2]
+ ]
+ ]
+ ]
+ ],
+ [
+ 'methods_mapping'=> [
+ $expectedMethodName => [
+ 'service' => $expectedService,
+ 'aliases' => [$expectedAlias1, $expectedAlias2]
+ ]
+ ]
+ ],
+ 'methods_mapping'
+ );
+ }
+
+ public function testShouldManageASimpleAliasDefinition()
+ {
+ $expectedMethodName = 'my-method-name';
+ $expectedAlias = 'my-method-name-alias';
+ $expectedService = 'my-service-id';
+
+ $this->assertProcessedConfigurationEquals(
+ [
+ [
+ 'methods_mapping' => [
+ $expectedMethodName => [
+ 'service' => $expectedService,
+ 'aliases' => $expectedAlias,
+ ]
+ ]
+ ]
+ ],
+ [
+ 'methods_mapping'=> [
+ $expectedMethodName => [
+ 'service' => $expectedService,
+ 'aliases' => [$expectedAlias]
+ ]
+ ]
+ ],
+ 'methods_mapping'
+ );
+ }
+}
diff --git a/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionTest.php b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionTest.php
new file mode 100644
index 0000000..7b78e5e
--- /dev/null
+++ b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionTest.php
@@ -0,0 +1,31 @@
+setDefinition($serviceId1, $this->createCustomMethodResolverDefinition());
+ $this->setDefinition($serviceId2, $this->createCustomMethodResolverDefinition());
+
+ $this->expectException(LogicException::class);
+ // Check that exception is for the second method
+ $this->expectExceptionMessage(
+ sprintf(
+ 'Only one method resolver could be defined, found following services : %s',
+ implode(', ', [$serviceId1, $serviceId2])
+ )
+ );
+
+ $this->load();
+ }
+}
diff --git a/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionWithConfigurationParsedTest.php b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionWithConfigurationParsedTest.php
new file mode 100644
index 0000000..c5c661e
--- /dev/null
+++ b/tests/Technical/DependencyInjection/JsonRpcHttpServerExtensionWithConfigurationParsedTest.php
@@ -0,0 +1,64 @@
+load();
+
+ // Assert custom resolver is an alias of the stub
+ $this->assertContainerBuilderHasParameter(
+ self::EXPECTED_HTTP_ENDPOINT_PATH_CONTAINER_PARAM,
+ JsonRpcHttpServerExtension::HTTP_ENDPOINT_PATH
+ );
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldNormalizeExternalServiceIdStringPassedForMethodResolver()
+ {
+ $myCustomResolverServiceId = 'my-custom-resolver';
+ $this->setDefinition($myCustomResolverServiceId, $this->createCustomMethodResolverDefinition());
+
+ $this->load(['method_resolver' => '@'.$myCustomResolverServiceId]);
+
+ $this->assertEndpointIsUsable();
+ }
+
+ public function testShouldNormalizeExternalServiceIdStringPassedForMethodMapping()
+ {
+ $jsonRpcMethodServiceId = uniqid();
+
+ $methodService = $this->createJsonRpcMethodDefinition();
+ $this->setDefinition($jsonRpcMethodServiceId, $methodService);
+
+
+ $this->load([
+ 'methods_mapping' => [
+ 'a-method' => '@'.$jsonRpcMethodServiceId
+ ]
+ ]);
+
+ $this->assertJsonRpcMethodServiceIsAvailable($jsonRpcMethodServiceId);
+
+ $this->assertEndpointIsUsable();
+ }
+}