Completed
Pull Request — master (#5)
by Marcel
01:52
created

Server   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 141
ccs 58
cts 58
cp 1
rs 10
c 0
b 0
f 0
wmc 24

8 Methods

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