.github/workflows/build.yml000064400000002715144760124670012003 0ustar00name: build on: [push, pull_request] env: FORCE_COLOR: 1 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: test: strategy: matrix: php: - 7.1 - 7.2 - 7.3 - 7.4 - 8.0 deps: - lowest - highest include: - php: 8.1 deps: highest composer-options: --ignore-platform-reqs exclude: # that config currently breaks as older PHPUnit cannot generate coverage on PHP 8 - php: 8 deps: lowest runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - uses: ramsey/composer-install@v1 with: dependency-versions: ${{ matrix.deps }} composer-options: ${{ matrix.composer-options }} - run: vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist lib --bootstrap vendor/autoload.php tests - uses: codecov/codecov-action@v1 release: needs: test if: github.repository_owner == 'felixfbecker' && github.event_name == 'push' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 - name: Install npm dependencies run: npm ci - name: Release run: npm run semantic-release LICENSE000064400000001362144760124670005566 0ustar00ISC License Copyright (c) 2016, Felix Frederick Becker Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. composer.json000064400000001431144760124670007300 0ustar00{ "name": "felixfbecker/advanced-json-rpc", "description": "A more advanced JSONRPC implementation", "type": "library", "license": "ISC", "authors": [ { "name": "Felix Becker", "email": "felix.b@outlook.com" } ], "autoload": { "psr-4": { "AdvancedJsonRpc\\": "lib/" } }, "autoload-dev": { "psr-4": { "AdvancedJsonRpc\\Tests\\": "tests/" } }, "require": { "php": "^7.1 || ^8.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" }, "require-dev": { "phpunit/phpunit": "^7.0 || ^8.0" }, "minimum-stability": "dev", "prefer-stable": true } lib/Dispatcher.php000064400000015260144760124670010130 0ustar00 ReflectionMethod[] * * @var ReflectionMethod */ private $methods; /** * @var \phpDocumentor\Reflection\DocBlockFactory */ private $docBlockFactory; /** * @var \phpDocumentor\Reflection\Types\ContextFactory */ private $contextFactory; /** * @param object $target The target object that should receive the method calls * @param string $delimiter A delimiter for method calls on properties, for example someProperty->someMethod */ public function __construct($target, $delimiter = '->') { $this->target = $target; $this->delimiter = $delimiter; $this->docBlockFactory = DocBlockFactory::createInstance(); $this->contextFactory = new Types\ContextFactory(); $this->mapper = new JsonMapper(); } /** * Calls the appropriate method handler for an incoming Message * * @param string|object $msg The incoming message * @return mixed */ public function dispatch($msg) { if (is_string($msg)) { $msg = json_decode($msg); if (json_last_error() !== JSON_ERROR_NONE) { throw new Error(json_last_error_msg(), ErrorCode::PARSE_ERROR); } } // Find out the object and function that should be called $obj = $this->target; $parts = explode($this->delimiter, $msg->method); // The function to call is always the last part of the method $fn = array_pop($parts); // For namespaced methods like textDocument/didOpen, call the didOpen method on the $textDocument property // For simple methods like initialize, shutdown, exit, this loop will simply not be entered and $obj will be // the target foreach ($parts as $part) { if (!isset($obj->$part)) { throw new Error("Method {$msg->method} is not implemented", ErrorCode::METHOD_NOT_FOUND); } $obj = $obj->$part; } if (!isset($this->methods[$msg->method])) { try { $method = new ReflectionMethod($obj, $fn); $this->methods[$msg->method] = $method; } catch (ReflectionException $e) { throw new Error($e->getMessage(), ErrorCode::METHOD_NOT_FOUND, null, $e); } } $method = $this->methods[$msg->method]; $parameters = $method->getParameters(); if ($method->getDocComment()) { $docBlock = $this->docBlockFactory->create( $method->getDocComment(), $this->contextFactory->createFromReflector($method->getDeclaringClass()) ); $paramTags = $docBlock->getTagsByName('param'); } $args = []; if (isset($msg->params)) { // Find out the position if (is_array($msg->params)) { $args = $msg->params; } else if (is_object($msg->params)) { foreach ($parameters as $pos => $parameter) { $value = null; foreach(get_object_vars($msg->params) as $key => $val) { if ($parameter->name === $key) { $value = $val; break; } } $args[$pos] = $value; } } else { throw new Error('Params must be structured or omitted', ErrorCode::INVALID_REQUEST); } foreach ($args as $position => $value) { try { // If the type is structured (array or object), map it with JsonMapper if (is_object($value)) { // Does the parameter have a type hint? $param = $parameters[$position]; if ($param->hasType()) { $paramType = $param->getType(); if ($paramType instanceof ReflectionNamedType) { // We have object data to map and want the class name. // This should not include the `?` if the type was nullable. $class = $paramType->getName(); } else { // Fallback for php 7.0, which is still supported (and doesn't have nullable). $class = (string)$paramType; } $value = $this->mapper->map($value, new $class()); } } else if (is_array($value) && isset($docBlock)) { // Get the array type from the DocBlock $type = $paramTags[$position]->getType(); // For union types, use the first one that is a class array (often it is SomeClass[]|null) if ($type instanceof Types\Compound) { for ($i = 0; $t = $type->get($i); $i++) { if ( $t instanceof Types\Array_ && $t->getValueType() instanceof Types\Object_ && (string)$t->getValueType() !== 'object' ) { $class = (string)$t->getValueType()->getFqsen(); $value = $this->mapper->mapArray($value, [], $class); break; } } } else if ($type instanceof Types\Array_) { $class = (string)$type->getValueType()->getFqsen(); $value = $this->mapper->mapArray($value, [], $class); } else { throw new Error('Type is not matching @param tag', ErrorCode::INVALID_PARAMS); } } } catch (JsonMapper_Exception $e) { throw new Error($e->getMessage(), ErrorCode::INVALID_PARAMS, null, $e); } $args[$position] = $value; } } ksort($args); $result = $obj->$fn(...$args); return $result; } } lib/Error.php000064400000001655144760124670007136 0ustar00data = $data; } } lib/ErrorCode.php000064400000002375144760124670007731 0ustar00id) && isset($msg->error); } /** * @param int|string $id * @param \AdvancedJsonRpc\Error $error */ public function __construct($id, Error $error) { parent::__construct($id); $this->error = $error; } } lib/Message.php000064400000003127144760124670007425 0ustar00method, $decoded->params ?? null); } else if (Request::isRequest($decoded)) { $obj = new Request($decoded->id, $decoded->method, $decoded->params ?? null); } else if (SuccessResponse::isSuccessResponse($decoded)) { $obj = new SuccessResponse($decoded->id, $decoded->result); } else if (ErrorResponse::isErrorResponse($decoded)) { $obj = new ErrorResponse($decoded->id, new Error($decoded->error->message, $decoded->error->code, $decoded->error->data ?? null)); } else { throw new Error('Invalid message', ErrorCode::INVALID_REQUEST); } return $obj; } public function __toString(): string { $encoded = json_encode($this); if ($encoded === false) { throw new Error(json_last_error_msg(), ErrorCode::INTERNAL_ERROR); } return $encoded; } } lib/Notification.php000064400000004333144760124670010467 0ustar00method); } /** * @param string $method * @param mixed $params */ public function __construct(string $method, $params = null) { $this->method = $method; $this->params = $params; } } lib/Request.php000064400000004157144760124670007475 0ustar00method); } /** * @param string|int $id * @param string $method * @param object|array $params */ public function __construct($id, string $method, $params = null) { $this->id = $id; $this->method = $method; $this->params = $params; } } lib/Response.php000064400000002171144760124670007635 0ustar00error)); } /** * @param int|string $id * @param mixed $result * @param ResponseError $error */ public function __construct($id) { $this->id = $id; } } lib/SuccessResponse.php000064400000002120144760124670011160 0ustar00result = $result; } }