Test Failed
Push — master ( 051a0a...29c8e1 )
by Sergey
08:17
created

RpcHandler::setThrowable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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