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