Passed
Pull Request — master (#2)
by Sergey
03:11
created

JsonRpcHandler::getCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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