Test Failed
Push — master ( 142697...051a0a )
by Sergey
08:41
created

RpcHandler::getException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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