Test Failed
Pull Request — master (#2)
by Sergey
03:54
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
            return '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "'
71
                . $e->getMessage() . '"}, "id": null}';
72
        }
73
74
        $resArr = [];
75
        foreach ($data as $rpc) {
76
            // TODO впилить паралельное выполнение, возможно amphp/amp
77
            $resArr[] = $this->oneRun(
78
                $model,
79
                $rpc
80
            );
81
        }
82
83
        $res = implode(',', $resArr);
84
85
        if ($rpcService->isBatch()) {
86
            $res = '[' . $res . ']';
87
        }
88
89
        return $res;
90
    }
91
92
    /**
93
     * @param RunModel $model
94
     * @param stdClass $rpc
95
     * @return string
96
     */
97
    private function oneRun(
98
        RunModel $model,
99
        stdClass $rpc
100
    ): string {
101
        $res = [
102
            'jsonrpc' => '2.0',
103
        ];
104
105
        $errorLevel = null;
106
107
        try {
108
            /** валидируем и парсим JsonRPC */
109
            $rpcObj = $this->getRpcService()->getRpc($rpc);
110
111
            /** Проверим авторизацию */
112
            $this->authCheck($rpcObj->getMethod(), $model->isResultAuth(), $model->getMethodsWithoutAuth());
113
114
            /** Пытаемся выполнить запрос */
115
            $res['result'] = $this->getApiExeService()->exe(
116
                $model,
117
                $rpcObj
118
            );
119
        } catch (InvalidAuthorizeException $e) {
120
            $res['error'] = [
121
                'code'    => -32001,
122
                'message' => $e->getMessage(),
123
            ];
124
            $errorLevel = LogLevel::INFO;
125
        } catch (ParseErrorException $e) {
126
            $res['error'] = [
127
                'code'    => -32700,
128
                'message' => 'Parse error: ' . $e->getMessage(),
129
            ];
130
            $errorLevel = LogLevel::ERROR;
131
        } catch (InvalidRequestException $e) {
132
            $res['error'] = [
133
                'code'    => -32600,
134
                'message' => 'Invalid Request: ' . $e->getMessage(),
135
            ];
136
            $errorLevel = LogLevel::ERROR;
137
        } catch (MethodNotFoundException $e) {
138
            $res['error'] = [
139
                'code'    => -32601,
140
                'message' => $e->getMessage(), // 'Method not found',
141
            ];
142
            $errorLevel = LogLevel::ERROR;
143
        } catch (InvalidParamsException $e) {
144
            $res['error'] = [
145
                'code'    => -32602,
146
                'message' => $e->getMessage(), // 'Invalid params',
147
                'data'    => $e->getData(),
148
            ];
149
            $errorLevel = LogLevel::ERROR;
150
        } catch (MethodErrorException $e) {
151
            $res['error'] = [
152
                'code'    => $e->getCode(),
153
                'message' => $e->getMessage(),
154
                'data'    => $e->getData(),
155
            ];
156
            $errorLevel = LogLevel::NOTICE;
157
        } catch (Throwable $t) {
158
            $res['error'] = [
159
                'code'    => -32603,
160
                'message' => 'Internal error: ' . $t->getMessage(),
161
                'data'    => [
162
                    'exception' => get_class($t),
163
                    'code'      => $t->getCode(),
164
                    'file'      => $t->getFile(),
165
                    'line'      => $t->getLine(),
166
                ]
167
            ];
168
            $errorLevel = LogLevel::EMERGENCY;
169
        }
170
171
        $this->log($errorLevel, $res);
172
173
        return $this->getJsonResult($rpc, $res);
174
    }
175
176
    /**
177
     * @param string $method
178
     * @param bool $resultAuth
179
     * @param string[] $methodsWithoutAuth
180
     */
181
    private function authCheck(string $method, bool $resultAuth, array $methodsWithoutAuth): void
182
    {
183
        if (
184
            $resultAuth === false
185
            && in_array($method, $methodsWithoutAuth, true) === false
186
        ) {
187
            throw new InvalidAuthorizeException(
188
                'Access denied, you are not authorized', // 'Доступ запрещен, вы не авторизованы'
189
            );
190
        }
191
    }
192
193
    /**
194
     * @param stdClass $rpc
195
     * @param mixed[] $res
196
     * @return string
197
     */
198
    private function getJsonResult(stdClass $rpc, array &$res): string
199
    {
200
        // ???
201
        $strId = 'error';
202
        if (isset($rpc->id)) {
203
            $res['id'] = $rpc->id;
204
            $strId = $rpc->id;
205
        }
206
207
        try {
208
            $result = json_encode($res, JSON_THROW_ON_ERROR);
209
        } catch (JsonException $e) {
210
            $result = '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "'
211
                . $e->getMessage() . '"}, "id": ' . $strId . '}';
212
        }
213
214
        return $result;
215
    }
216
217
    /**
218
     * @param string|null $errorLevel
219
     * @param mixed[]     $res
220
     */
221
    private function log(?string $errorLevel, array $res): void
222
    {
223
        $logger = $this->getLogger();
224
        if ($errorLevel !== null && $logger instanceof LoggerInterface) {
225
            $error = $res['error'] ?? [];
226
            $logger->log($errorLevel, $error['message'], $error);
227
        }
228
    }
229
230
    /**
231
     * @return LoggerInterface|null
232
     */
233
    public function getLogger(): ?LoggerInterface
234
    {
235
        return $this->logger;
236
    }
237
238
    /**
239
     * @param LoggerInterface|null $logger
240
     */
241
    public function setLogger(?LoggerInterface $logger): void
242
    {
243
        $this->logger = $logger;
244
    }
245
246
    /**
247
     * @return RpcService
248
     */
249
    public function getRpcService(): RpcService
250
    {
251
        return $this->rpcService;
252
    }
253
254
    /**
255
     * @return ApiExecService
256
     */
257
    public function getApiExeService(): ApiExecService
258
    {
259
        return $this->apiExecService;
260
    }
261
}
262