3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
17 * Execute a JSON-RPC request and return a result.
19 * This adapter handles decoding, encoding, and conversion of exceptions.
22 * $input = '{"jsonrpc":"2.0","method":"greet","id":1}';
23 * $output = JsonRpc::run($input, function(string $method, array $params) {
24 * if ($method === 'greet') return 'hello world';
25 * else throw new \InvalidArgumentException('Method not found', -32601);
27 * assert $output === '{"jsonrpc":"2.0","result":"hello world","id":1}';
30 * @param string $requestLine
31 * JSON formatted RPC request
32 * @param callable $dispatcher
33 * Dispatch function - given a parsed/well-formed request, compute the result.
34 * Signature: function(string $method, mixed $params): mixed
36 * JSON formatted RPC response
38 public static function run(string $requestLine, callable
$dispatcher): string {
39 $parsed = \
json_decode($requestLine, TRUE);
41 if ($parsed === NULL) {
42 throw new \
InvalidArgumentException('Parse error', -32700);
45 if (isset($parsed[0])) {
47 foreach ($parsed as $request) {
48 $response[] = static::handleMethodCall($request, $dispatcher);
51 elseif (isset($parsed['method'])) {
52 $response = static::handleMethodCall($parsed, $dispatcher);
55 // [sic] 'Invalid Request' title-case is anomalous but dictated by standard.
56 throw new \
InvalidArgumentException('Invalid Request', -32600);
59 return \
json_encode($response);
62 protected static function handleMethodCall($request, $dispatcher): array {
64 if ($request === NULL) {
65 throw new \
InvalidArgumentException('Parse error', -32700);
67 if (($request['jsonrpc'] ??
'') !== '2.0' ||
!is_string($request['method'])) {
68 // [sic] 'Invalid Request' title-case is anomalous but dictated by standard.
69 throw new \
InvalidArgumentException('Invalid Request', -32600);
71 if (isset($request['params']) && !is_array($request['params'])) {
72 throw new \
InvalidArgumentException('Invalid params', -32602);
75 $result = $dispatcher($request['method'], $request['params'] ??
[]);
76 return static::createResponseSuccess($request, $result);
78 catch (\Throwable
$t) {
79 return static::createResponseError($request, $t);
84 * Create a response object (successful).
86 * @link https://www.jsonrpc.org/specification#response_object
87 * @param array{jsonrpc: string, method: string, params: array, id: ?mixed} $request
88 * @param mixed $result
89 * The result-value of the method call.
90 * @return array{jsonrpc: string, result: mixed, id: ?mixed}
92 public static function createResponseSuccess(array $request, $result): array {
93 $id = array_key_exists('id', $request) ?
['id' => $request['id']] : [];
101 * Create a response object (unsuccessful).
103 * @link https://www.jsonrpc.org/specification#response_object
104 * @param array{jsonrpc: string, method: string, params: array, id: ?mixed} $request
105 * @param \Throwable $t
106 * The exception which caused the request to fail.
107 * @return array{jsonrpc: string, error: array, id: ?mixed}
109 public static function createResponseError(array $request, \Throwable
$t): array {
110 $isJsonErrorCode = $t->getCode() >= -32999 && $t->getCode() <= -32000;
111 $errorData = \CRM_Core_Config
::singleton()->debug
112 ?
['class' => get_class($t), 'trace' => $t->getTraceAsString()]
114 $id = array_key_exists('id', $request) ?
['id' => $request['id']] : [];
116 'jsonrpc' => $request['jsonrpc'] ??
'2.0',
118 'code' => $isJsonErrorCode ?
$t->getCode() : -32099,
119 'message' => $t->getMessage(),
120 'data' => $errorData,