Completed
Push — master ( f0de6b...b60992 )
by Marcel
02:00
created

Server::single()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

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