Passed
Push — master ( b22a88...886da7 )
by Marcel
02:28
created

Server::batch()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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