Passed
Push — master ( f0ad5e...b22a88 )
by Marcel
04:24
created

Server   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 145
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 57
dl 0
loc 145
ccs 0
cts 58
cp 0
rs 10
c 4
b 0
f 0
wmc 24

8 Methods

Rating   Name   Duplication   Size   Complexity  
A tooManyBatchRequests() 0 5 2
A __construct() 0 7 1
A batch() 0 15 4
A attach() 0 9 2
A end() 0 4 3
A set() 0 9 2
A run() 0 17 4
A single() 0 32 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UMA\JsonRpc;
6
7
use LogicException;
8
use Psr\Container\ContainerExceptionInterface;
9
use Psr\Container\ContainerInterface;
10
use Psr\Container\NotFoundExceptionInterface;
11
use stdClass;
12
use TypeError;
13
use UMA\JsonRpc\Internal\Assert;
14
use UMA\JsonRpc\Internal\Input;
15
use UMA\JsonRpc\Internal\MiddlewareStack;
16
use UMA\JsonRpc\Internal\Validator;
17
18
class Server
19
{
20
    /**
21
     * @var ContainerInterface
22
     */
23
    protected $container;
24
25
    /**
26
     * @var string[]
27
     */
28
    protected $methods;
29
30
    /**
31
     * @var string[]
32
     */
33
    protected $middlewares;
34
35
    /**
36
     * @var int|null
37
     */
38
    protected $batchLimit;
39
40
    /**
41
     * @var bool
42
     */
43
    protected $simdJson;
44
45
    public function __construct(ContainerInterface $container, int $batchLimit = null, bool $simdJson = false)
46
    {
47
        $this->container = $container;
48
        $this->simdJson = $simdJson;
49
        $this->batchLimit = $batchLimit;
50
        $this->methods = [];
51
        $this->middlewares = [];
52
    }
53
54
    public function set(string $method, string $serviceId): Server
55
    {
56
        if (!$this->container->has($serviceId)) {
57
            throw new LogicException("Cannot find service '$serviceId' in the container");
58
        }
59
60
        $this->methods[$method] = $serviceId;
61
62
        return $this;
63
    }
64
65
    public function attach(string $serviceId): Server
66
    {
67
        if (!$this->container->has($serviceId)) {
68
            throw new LogicException("Cannot find service '$serviceId' in the container");
69
        }
70
71
        $this->middlewares[$serviceId] = null;
72
73
        return $this;
74
    }
75
76
    /**
77
     * @throws TypeError
78
     */
79
    public function run(string $raw): ?string
80
    {
81
        $input = Input::fromString($raw, $this->simdJson);
82
83
        if (!$input->parsable()) {
84
            return self::end(Error::parsing());
85
        }
86
87
        if ($input->isArray()) {
88
            if ($this->tooManyBatchRequests($input)) {
89
                return self::end(Error::tooManyBatchRequests($this->batchLimit));
90
            }
91
92
            return $this->batch($input);
93
        }
94
95
        return $this->single($input);
96
    }
97
98
    protected function batch(Input $input): ?string
99
    {
100
        \assert($input->isArray());
101
102
        $responses = [];
103
        foreach ($input->data() as $request) {
104
            $pseudoInput = Input::fromSafeData($request);
105
106
            if (null !== $response = $this->single($pseudoInput)) {
107
                $responses[] = $response;
108
            }
109
        }
110
111
        return empty($responses) ?
112
            null : \sprintf('[%s]', \implode(',', $responses));
113
    }
114
115
    /**
116
     * @throws TypeError
117
     */
118
    protected function single(Input $input): ?string
119
    {
120
        if (!$input->isRpcRequest()) {
121
            return self::end(Error::invalidRequest());
122
        }
123
124
        $request = new Request($input);
125
126
        if (!\array_key_exists($request->method(), $this->methods)) {
127
            return self::end(Error::unknownMethod($request->id()), $request);
128
        }
129
130
        try {
131
            $procedure = Assert::isProcedure(
132
                $this->container->get($this->methods[$request->method()])
133
            );
134
        } catch (ContainerExceptionInterface | NotFoundExceptionInterface $e) {
135
            return self::end(Error::internal($request->id()), $request);
136
        }
137
138
        if ($procedure->getSpec() instanceof stdClass && !Validator::validate($procedure->getSpec(), $request->params())) {
139
            return self::end(Error::invalidParams($request->id()), $request);
140
        }
141
142
        $stack = MiddlewareStack::compose(
143
            $procedure,
144
            ...\array_map(function(string $serviceId) {
145
                return $this->container->get($serviceId);
146
            }, \array_keys($this->middlewares))
147
        );
148
149
        return self::end($stack($request), $request);
150
    }
151
152
    protected function tooManyBatchRequests(Input $input): bool
153
    {
154
        \assert($input->isArray());
155
156
        return \is_int($this->batchLimit) && $this->batchLimit < \count($input->data());
157
    }
158
159
    protected static function end(Response $response, Request $request = null): ?string
160
    {
161
        return $request instanceof Request && null === $request->id() ?
162
            null : \json_encode($response);
163
    }
164
}
165