RpcHandler::setThrowable()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Onnov\JsonRpcServer;
6
7
use JsonException;
8
use JsonMapper;
9
use Onnov\JsonRpcServer\Definition\RpcAuthDefinition;
10
use Onnov\JsonRpcServer\Error\RpcError;
11
use Onnov\JsonRpcServer\Exception\InternalErrorException;
12
use Onnov\JsonRpcServer\Model\RpcRun;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LogLevel;
15
use stdClass;
16
use Throwable;
17
use Onnov\JsonRpcServer\Exception\InvalidAuthorizeException;
18
use Onnov\JsonRpcServer\Exception\InvalidParamsException;
19
use Onnov\JsonRpcServer\Exception\InvalidRequestException;
20
use Onnov\JsonRpcServer\Exception\MethodErrorException;
21
use Onnov\JsonRpcServer\Exception\MethodNotFoundException;
22
use Onnov\JsonRpcServer\Exception\ParseErrorException;
23
use Onnov\JsonRpcServer\Service\ApiExecService;
24
use Onnov\JsonRpcServer\Service\RpcService;
25
use Onnov\JsonRpcServer\Validator\JsonRpcSchema;
26
use Onnov\JsonRpcServer\Validator\JsonSchemaValidator;
27
28
/**
29
 * Class RpcHandler
30
 *
31
 * @package Onnov\JsonRpcServer
32
 */
33
class RpcHandler
34
{
35
    private ?Throwable $throwable = null;
36
37
    private ?LoggerInterface $logger;
38
    private RpcService $rpcService;
39
    private ApiExecService $apiExecService;
40
    private RpcError $rpcError;
41
    private bool $detailError = false;
42
43
    /**
44
     * JsonRpcHandler constructor.
45
     *
46
     * @param LoggerInterface|null $logger
47
     */
48 9
    public function __construct(LoggerInterface $logger = null)
49
    {
50 9
        $this->logger = $logger;
51
52 9
        $validator = new JsonSchemaValidator();
53 9
        $rpcSchema = new JsonRpcSchema();
54 9
        $mapper = new JsonMapper();
55
56 9
        $this->rpcService = new RpcService($validator, $rpcSchema, $mapper);
57 9
        $this->apiExecService = new ApiExecService($validator, $rpcSchema, $mapper);
58
    }
59
60
    /**
61
     * @param  RpcRun $rpcRun
62
     * @return string
63
     * @throws JsonException
64
     */
65 9
    public function run(RpcRun $rpcRun): string
66
    {
67 9
        if (isset($this->rpcError) === false) {
68 9
            $this->setRpcError(new RpcError($rpcRun->getAuth()->getAuthError()));
69
        }
70 9
        $rpcService = $this->getRpcService();
71
72
        /**
73
         * Парсим
74
         */
75
        try {
76 9
            $data = $rpcService->jsonParse($rpcRun->getJson());
77
78 8
            $resArr = [];
79 8
            foreach ($data as $rpc) {
80
                switch (true) {
81 8
                case ($rpc instanceof stdClass):
82
                    // TODO впилить паралельное выполнение, возможно amphp/amp
83 8
                    $resArr[] = $this->oneRun(
84 8
                        $rpcRun,
85 8
                        $rpc
86 8
                    );
87 8
                    break;
88
                default:
89 1
                    $resArr[] = $this->getJsonStrError();
90
                }
91
            }
92
93 8
            $res = implode(',', $resArr);
94
95 8
            if ($rpcService->isBatch()) {
96 8
                $res = '[' . $res . ']';
97
            }
98 1
        } catch (ParseErrorException $e) {
99 1
            $res = $this->getJsonStrError($e->getPrevious());
100
        }
101
102 9
        return $res;
103
    }
104
105
    /**
106
     * @param  RpcRun   $rpcRun
107
     * @param  stdClass $rpc
108
     * @return string
109
     * @throws JsonException
110
     */
111 8
    private function oneRun(
112
        RpcRun $rpcRun,
113
        stdClass $rpc
114
    ): string {
115 8
        $res = [
116 8
            'jsonrpc' => '2.0',
117 8
        ];
118
119 8
        $error = null;
120
121
        try {
122
            /**
123
             * валидируем и парсим JsonRPC
124
             */
125 8
            $rpcObj = $this->getRpcService()->getRpc($rpc);
126
127
            /**
128
             * Проверим авторизацию
129
             */
130 7
            $this->authCheck($rpcObj->getMethod(), $rpcRun->getAuth());
131
132
            /**
133
             * Пытаемся выполнить запрос
134
             */
135 6
            $res['result'] = $this->getApiExeService()->exe(
136 6
                $rpcRun,
137 6
                $rpcObj
138 6
            );
139 4
        } catch (InvalidAuthorizeException | MethodNotFoundException $e) {
140 2
            $this->setThrowable($e);
141 2
            $error = $this
142 2
                ->getRpcError()
143 2
                ->getErrorByName($this->getExceptionName($e), $e);
144
        } catch (
145 2
            InternalErrorException
146 2
            | InvalidParamsException
147 2
            | InvalidRequestException
148 2
            | MethodErrorException
149 2
            | ParseErrorException $e
150
        ) {
151 2
            $this->setThrowable($e);
152 2
            $error = $this
153 2
                ->getRpcError()
154 2
                ->getErrorByName($this->getExceptionName($e), $e);
155
156 2
            if ($e->getData() !== null) {
157 2
                $error->setData((object)$e->getData());
158
            }
159
        } catch (Throwable $t) {
160
            $this->setThrowable($t);
161
            $error = $this
162
                ->getRpcError()
163
                ->getErrorByName('Throwable');
164
            $error->setData(
165
                (object)[
166
                    'exception' => get_class($t),
167
                    'code' => $t->getCode(),
168
                    'message' => $t->getMessage(),
169
                    'file' => $t->getFile(),
170
                    'line' => $t->getLine(),
171
                ]
172
            );
173
        }
174
175 8
        if ($error !== null) {
176 4
            $res['error'] = (object)[
177 4
                'code' => $error->getCode(),
178 4
                'message' => $error->getMessage(),
179 4
            ];
180 4
            if ($this->isDetailError() && $error->getData() !== null) {
181 2
                $res['error']->data = $error->getData();
182
            }
183 4
            $this->log($error->getLogLevel(), $res);
184
        }
185
186 8
        return $this->getJsonResult($rpc, $res);
187
    }
188
189
    /**
190
     * @param  Throwable $throw
191
     * @return string
192
     */
193 4
    private function getExceptionName(Throwable $throw): string
194
    {
195 4
        $exception = get_class($throw);
196
197 4
        return substr($exception, (int)strrpos($exception, '\\') + 1);
198
    }
199
200
    /**
201
     * @param string            $method
202
     * @param RpcAuthDefinition $auth
203
     */
204 7
    private function authCheck(string $method, RpcAuthDefinition $auth): void
205
    {
206 7
        if ($auth->isResultAuth() === false
207 7
            && in_array($method, $auth->getProcWithoutAuth(), true) === false
208
        ) {
209 1
            throw new InvalidAuthorizeException();
210
        }
211
    }
212
213
    /**
214
     * @param  stdClass $rpc
215
     * @param  mixed[]  $res
216
     * @return string
217
     * @throws JsonException
218
     */
219 8
    private function getJsonResult(stdClass $rpc, array &$res): string
220
    {
221 8
        if (isset($rpc->id)) {
222 8
            $res['id'] = $rpc->id;
223
        }
224
225
        try {
226 8
            $result = json_encode($res, JSON_THROW_ON_ERROR);
227
        } catch (JsonException $e) {
228
            $result = $this->getJsonStrError($e, $rpc->id ?? 'error');
229
        }
230
231 8
        return $result;
232
    }
233
234
    /**
235
     * @param  Throwable|null $err
236
     * @param  mixed          $id
237
     * @return string
238
     * @throws JsonException
239
     */
240 2
    private function getJsonStrError(Throwable $err = null, $id = 'error'): string
241
    {
242 2
        if (gettype($id) === 'string') {
243 2
            $id = '"' . $id . '"';
244
        }
245
246 2
        $data = '';
247 2
        $msg = 'Parse error';
248 2
        if ($err !== null) {
249 1
            $data = ', "data": {"code": ' . $err->getCode() . ', "message": "' . $err->getMessage() . '"}';
250 1
            $msg = 'Parse error (' . $err->getMessage() . ')';
251
        }
252
253 2
        $res = [
254 2
            '{"jsonrpc": "2.0",',
255 2
            '"error":',
256 2
            '{"code": -32700, "message": "Parse error"' . $data . '},',
257 2
            '"id": ' . $id . '}',
258 2
        ];
259
260 2
        $this->log(LogLevel::ERROR, ['error' => (object)['message' => $msg]]);
261
262 2
        return implode(' ', $res);
263
    }
264
265
    /**
266
     * @param  string|null $errorLevel
267
     * @param  mixed[]     $res
268
     * @throws JsonException
269
     */
270 6
    private function log(?string $errorLevel, array $res): void
271
    {
272 6
        $logger = $this->getLogger();
273 6
        if ($errorLevel !== null && $logger instanceof LoggerInterface) {
274
            $error = json_decode(
275
                json_encode($res['error'] ?? [], JSON_THROW_ON_ERROR),
276
                true
277
            );
278
            $logger->log($errorLevel, $error->message ?? 'no message', $error);
279
        }
280
    }
281
282
    /**
283
     * @return LoggerInterface|null
284
     */
285 6
    public function getLogger(): ?LoggerInterface
286
    {
287 6
        return $this->logger;
288
    }
289
290
    /**
291
     * @param LoggerInterface|null $logger
292
     */
293
    public function setLogger(?LoggerInterface $logger): void
294
    {
295
        $this->logger = $logger;
296
    }
297
298
    /**
299
     * @return RpcService
300
     */
301 9
    public function getRpcService(): RpcService
302
    {
303 9
        return $this->rpcService;
304
    }
305
306
    /**
307
     * @return ApiExecService
308
     */
309 6
    public function getApiExeService(): ApiExecService
310
    {
311 6
        return $this->apiExecService;
312
    }
313
314
    /**
315
     * @return RpcError
316
     */
317 4
    public function getRpcError(): RpcError
318
    {
319 4
        return $this->rpcError;
320
    }
321
322
    /**
323
     * @param RpcError $rpcError
324
     */
325 9
    public function setRpcError(RpcError $rpcError): void
326
    {
327 9
        $this->rpcError = $rpcError;
328
    }
329
330
    /**
331
     * @return Throwable|null
332
     */
333
    public function getThrowable(): ?Throwable
334
    {
335
        return $this->throwable;
336
    }
337
338
    /**
339
     * @param  Throwable|null $throwable
340
     * @return RpcHandler
341
     */
342 4
    public function setThrowable(?Throwable $throwable): RpcHandler
343
    {
344 4
        $this->throwable = $throwable;
345 4
        return $this;
346
    }
347
348
    /**
349
     * @return ApiExecService
350
     */
351
    public function getApiExecService(): ApiExecService
352
    {
353
        return $this->apiExecService;
354
    }
355
356
    /**
357
     * @param  ApiExecService $apiExecService
358
     * @return RpcHandler
359
     */
360
    public function setApiExecService(ApiExecService $apiExecService): RpcHandler
361
    {
362
        $this->apiExecService = $apiExecService;
363
        return $this;
364
    }
365
366
    /**
367
     * @return bool
368
     */
369 4
    public function isDetailError(): bool
370
    {
371 4
        return $this->detailError;
372
    }
373
374
    /**
375
     * @param  bool $detailError
376
     * @return RpcHandler
377
     */
378 2
    public function setDetailError(bool $detailError): RpcHandler
379
    {
380 2
        $this->detailError = $detailError;
381 2
        return $this;
382
    }
383
}
384