.travis.yml000064400000001266144760121320006662 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+$/ LICENSE000064400000001362144760121320005553 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.json000064400000001374144760121320007273 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 || ^2.0", "phpdocumentor/reflection-docblock": "^4.0.0 || ^5.0.0" }, "require-dev": { "phpunit/phpunit": "^6.0.0" }, "minimum-stability": "dev", "prefer-stable": true } lib/Dispatcher.php000064400000015306144760121320010116 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.php000064400000001655144760121320007123 0ustar00data = $data; } } lib/ErrorCode.php000064400000002375144760121320007716 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.php000064400000003127144760121320007412 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.php000064400000004333144760121320010454 0ustar00method); } /** * @param string $method * @param mixed $params */ public function __construct(string $method, $params = null) { $this->method = $method; $this->params = $params; } } lib/Request.php000064400000004157144760121320007462 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.php000064400000002171144760121320007622 0ustar00error)); } /** * @param int|string $id * @param mixed $result * @param ResponseError $error */ public function __construct($id) { $this->id = $id; } } lib/SuccessResponse.php000064400000002120144760121320011145 0ustar00result = $result; } }