.travis.yml000064400000001266144760113550006670 0ustar00language: php php: - '7.0' - '7.2' env: global: - FORCE_COLOR=1 cache: directories: - $HOME/.composer/cache - $HOME/.npm install: - composer install --prefer-dist script: - vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist lib --bootstrap vendor/autoload.php tests after_success: - bash <(curl -s https://codecov.io/bash) jobs: include: - stage: release language: node_js node_js: '8' install: - npm ci script: - npm run semantic-release after_success: false stages: - test - name: release if: branch = master AND type = push AND fork = false branches: except: - /^v\d+\.\d+\.\d+$/ LICENSE000064400000001362144760113550005561 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.json000064400000001352144760113550007275 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.0", "netresearch/jsonmapper": "^1.0", "phpdocumentor/reflection-docblock": "^4.0.0" }, "require-dev": { "phpunit/phpunit": "^6.0.0" }, "minimum-stability": "dev", "prefer-stable": true } lib/Dispatcher.php000064400000015306144760113550010124 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.php000064400000001655144760113550007131 0ustar00data = $data; } } lib/ErrorCode.php000064400000002375144760113550007724 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.php000064400000003127144760113550007420 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.php000064400000004333144760113550010462 0ustar00method); } /** * @param string $method * @param mixed $params */ public function __construct(string $method, $params = null) { $this->method = $method; $this->params = $params; } } lib/Request.php000064400000004157144760113550007470 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.php000064400000002171144760113550007630 0ustar00error)); } /** * @param int|string $id * @param mixed $result * @param ResponseError $error */ public function __construct($id) { $this->id = $id; } } lib/SuccessResponse.php000064400000002120144760113550011153 0ustar00result = $result; } }