Completed
Push — master ( 39b3fc...3ebb26 )
by Sergey
12:04 queued 07:06
created

RpcHandler   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Test Coverage

Coverage 83.33%

Importance

Changes 5
Bugs 4 Features 0
Metric Value
eloc 113
c 5
b 4
f 0
dl 0
loc 286
ccs 90
cts 108
cp 0.8333
rs 9.76
wmc 33

14 Methods

Rating   Name   Duplication   Size   Complexity  
A log() 0 9 3
A getExceptionName() 0 5 1
B oneRun() 0 65 7
A authCheck() 0 7 3
A setLogger() 0 3 1
A getRpcService() 0 3 1
A getRpcError() 0 3 1
A setRpcError() 0 3 1
B run() 0 36 6
A getApiExeService() 0 3 1
A getJsonResult() 0 13 3
A __construct() 0 10 1
A getLogger() 0 3 1
A getJsonStrError() 0 23 3
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 LoggerInterface|null */
35
    private $logger;
36
37
    /** @var RpcService */
38
    private $rpcService;
39
40
    /** @var ApiExecService */
41
    private $apiExecService;
42
43
    /** @var RpcError */
44
    private $rpcError;
45
46
    /**
47
     * JsonRpcHandler constructor.
48
     * @param LoggerInterface|null $logger
49
     */
50 9
    public function __construct(LoggerInterface $logger = null)
51
    {
52 9
        $this->logger = $logger;
53
54 9
        $validator = new JsonSchemaValidator();
55 9
        $rpcSchema = new JsonRpcSchema();
56 9
        $mapper = new JsonMapper();
57
58 9
        $this->rpcService = new RpcService($validator, $rpcSchema, $mapper);
59 9
        $this->apiExecService = new ApiExecService($validator, $rpcSchema, $mapper);
60 9
    }
61
62
    /**
63
     * @param RpcRun $rpcRun
64
     * @return string
65
     * @throws JsonException
66
     */
67 9
    public function run(RpcRun $rpcRun): string
68
    {
69 9
        if (isset($this->rpcError) === false) {
70 9
            $this->setRpcError(new RpcError($rpcRun->getAuth()->getAuthError()));
71
        }
72 9
        $rpcService = $this->getRpcService();
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
                            $rpc
86
                        );
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
        $res = [
116 8
            'jsonrpc' => '2.0',
117
        ];
118
119 8
        $error = null;
120
121
        try {
122
            /** валидируем и парсим JsonRPC */
123 8
            $rpcObj = $this->getRpcService()->getRpc($rpc);
124
125
            /** Проверим авторизацию */
126 7
            $this->authCheck($rpcObj->getMethod(), $rpcRun->getAuth());
127
128
            /** Пытаемся выполнить запрос */
129 6
            $res['result'] = $this->getApiExeService()->exe(
130 6
                $rpcRun,
131
                $rpcObj
132
            );
133 4
        } catch (InvalidAuthorizeException | MethodNotFoundException $e) {
134
            $error = $this
135 2
                ->getRpcError()
136 2
                ->getErrorByName($this->getExceptionName($e), $e);
137
        } catch (
138 2
            InternalErrorException
139
            | InvalidParamsException
140
            | InvalidRequestException
141
            | MethodErrorException
142
            | ParseErrorException $e
143
        ) {
144
            $error = $this
145 2
                ->getRpcError()
146 2
                ->getErrorByName($this->getExceptionName($e), $e);
147
148 2
            if ($e->getData() !== null) {
149 2
                $error->setData((object)$e->getData());
150
            }
151
        } catch (Throwable $t) {
152
            $error = $this
153
                ->getRpcError()
154
                ->getErrorByName('Throwable');
155
            $error->setData((object)[
156
                'exception' => get_class($t),
157
                'code'      => $t->getCode(),
158
                'message'   => $t->getMessage(),
159
                'file'      => $t->getFile(),
160
                'line'      => $t->getLine(),
161
            ]);
162
        }
163
164 8
        if ($error !== null) {
165 4
            $res['error'] = (object)[
166 4
                'code' => $error->getCode(),
167 4
                'message' => $error->getMessage(),
168
            ];
169 4
            if ($error->getData() !== null) {
170 2
                $res['error']->data = $error->getData();
171
            }
172 4
            $this->log($error->getLogLevel(), $res);
173
        }
174
175 8
        return $this->getJsonResult($rpc, $res);
176
    }
177
178
    /**
179
     * @param Throwable $throw
180
     * @return string
181
     */
182 4
    private function getExceptionName(Throwable $throw): string
183
    {
184 4
        $exception = get_class($throw);
185
186 4
        return substr($exception, (int)strrpos($exception, '\\') + 1);
187
    }
188
189
    /**
190
     * @param string $method
191
     * @param RpcAuthDefinition $auth
192
     */
193 7
    private function authCheck(string $method, RpcAuthDefinition $auth): void
194
    {
195
        if (
196 7
            $auth->isResultAuth() === false
197 7
            && in_array($method, $auth->getProcWithoutAuth(), true) === false
198
        ) {
199 1
            throw new InvalidAuthorizeException();
200
        }
201 6
    }
202
203
    /**
204
     * @param stdClass $rpc
205
     * @param mixed[] $res
206
     * @return string
207
     * @throws JsonException
208
     */
209 8
    private function getJsonResult(stdClass $rpc, array &$res): string
210
    {
211 8
        if (isset($rpc->id)) {
212 8
            $res['id'] = $rpc->id;
213
        }
214
215
        try {
216 8
            $result = json_encode($res, JSON_THROW_ON_ERROR);
217
        } catch (JsonException $e) {
218
            $result = $this->getJsonStrError($e, $rpc->id ?? 'error');
219
        }
220
221 8
        return $result;
222
    }
223
224
    /**
225
     * @param Throwable|null $err
226
     * @param mixed $id
227
     * @return string
228
     * @throws JsonException
229
     */
230 2
    private function getJsonStrError(Throwable $err = null, $id = 'error'): string
231
    {
232 2
        if (gettype($id) === 'string') {
233 2
            $id = '"' . $id . '"';
234
        }
235
236 2
        $data = '';
237 2
        $msg = 'Parse error';
238 2
        if ($err !== null) {
239 1
            $data = ', "data" => {"code": ' . $err->getCode() . ', "message": "' . $err->getMessage() . '"}';
240 1
            $msg = 'Parse error (' . $err->getMessage() . ')';
241
        }
242
243
        $res = [
244 2
            '{"jsonrpc": "2.0",',
245 2
            '"error":',
246 2
            '{"code": -32700, "message": "Parse error"' . $data . '},',
247 2
            '"id": ' . $id . '}',
248
        ];
249
250 2
        $this->log(LogLevel::ERROR, ['error' => (object)['message' => $msg]]);
251
252 2
        return implode(' ', $res);
253
    }
254
255
    /**
256
     * @param string|null $errorLevel
257
     * @param mixed[] $res
258
     * @throws JsonException
259
     */
260 6
    private function log(?string $errorLevel, array $res): void
261
    {
262 6
        $logger = $this->getLogger();
263 6
        if ($errorLevel !== null && $logger instanceof LoggerInterface) {
264
            $error = json_decode(
265
                json_encode($res['error'] ?? [], JSON_THROW_ON_ERROR),
266
                true
267
            );
268
            $logger->log($errorLevel, $error->message ?? 'no message', $error);
269
        }
270 6
    }
271
272
    /**
273
     * @return LoggerInterface|null
274
     */
275 6
    public function getLogger(): ?LoggerInterface
276
    {
277 6
        return $this->logger;
278
    }
279
280
    /**
281
     * @param LoggerInterface|null $logger
282
     */
283
    public function setLogger(?LoggerInterface $logger): void
284
    {
285
        $this->logger = $logger;
286
    }
287
288
    /**
289
     * @return RpcService
290
     */
291 9
    public function getRpcService(): RpcService
292
    {
293 9
        return $this->rpcService;
294
    }
295
296
    /**
297
     * @return ApiExecService
298
     */
299 6
    public function getApiExeService(): ApiExecService
300
    {
301 6
        return $this->apiExecService;
302
    }
303
304
    /**
305
     * @return RpcError
306
     */
307 4
    public function getRpcError(): RpcError
308
    {
309 4
        return $this->rpcError;
310
    }
311
312
    /**
313
     * @param RpcError $rpcError
314
     */
315 9
    public function setRpcError(RpcError $rpcError): void
316
    {
317 9
        $this->rpcError = $rpcError;
318 9
    }
319
}
320