Test Failed
Pull Request — master (#2)
by Sergey
03:27
created

JsonRpcHandler::getRpc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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