Passed
Pull Request — master (#11)
by Joao
02:01
created

OpenApiRouteList::generateRoutes()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 8.0551

Importance

Changes 0
Metric Value
cc 8
eloc 22
nc 20
nop 0
dl 0
loc 37
ccs 19
cts 21
cp 0.9048
crap 8.0551
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
4
namespace ByJG\RestServer\Route;
5
6
use ByJG\Cache\Psr16\NoCacheEngine;
7
use ByJG\RestServer\Exception\OperationIdInvalidException;
8
use ByJG\RestServer\Exception\SchemaInvalidException;
9
use ByJG\RestServer\Exception\SchemaNotFoundException;
10
use ByJG\RestServer\OutputProcessor\BaseOutputProcessor;
11
use ByJG\RestServer\OutputProcessor\JsonOutputProcessor;
12
use ByJG\Serializer\SerializerObject;
13
use ByJG\Util\Uri;
14
use Psr\SimpleCache\CacheInterface;
15
use Psr\SimpleCache\InvalidArgumentException;
16
17
class OpenApiRouteList extends RouteList
18
{
19
    protected $cache;
20
    protected $schema;
21
    protected $defaultProcessor;
22
    protected $overrideOutputProcessor = [];
23
24
    /**
25
     * @param $openApiDefinition
26
     * @param string $defaultProcessor
27
     * @param CacheInterface|null $cache
28
     * @throws InvalidArgumentException
29
     * @throws OperationIdInvalidException
30
     * @throws SchemaInvalidException
31
     * @throws SchemaNotFoundException
32
     */
33 5
    public function __construct($openApiDefinition)
34
    {
35 5
        if (!file_exists($openApiDefinition)) {
36
            throw new SchemaNotFoundException("Schema '$openApiDefinition' not found");
37
        }
38
39 5
        $ext = substr(strrchr($openApiDefinition, "."), 1);
40 5
        $contents = file_get_contents($openApiDefinition);
41
42 5
        if ($ext == "json") {
43 3
            $this->schema = SerializerObject::instance($contents)->fromJson()->serialize();
44 2
        } elseif ($ext == "yaml" || $ext == "yml") {
45 2
            $this->schema = SerializerObject::instance($contents)->fromYaml()->serialize();
46
        } else {
47
            throw new SchemaInvalidException(
48
                "Cannot determine file type. Valids extensions are 'json', 'yaml' or 'yml'"
49
            );
50
        }
51
52 5
        if (!isset($this->schema['paths'])) {
53
            throw new SchemaInvalidException("Schema '$openApiDefinition' is invalid");
54
        }
55
56 5
        $this->cache = new NoCacheEngine();
57
58 5
        $this->defaultProcessor = JsonOutputProcessor::class;
59
    }
60
61
    /**
62
     * @param $method
63
     * @param $path
64
     * @param string $processor
65
     * @return $this
66
     */
67 2
    public function withOutputProcessorForRoute($method, $path, $processor)
68
    {
69 2
        $this->overrideOutputProcessor[strtoupper($method) . " " . $path] = $processor;
70 2
        return $this;
71
    }
72
73 2
    public function withOutputProcessorForMimeType($mimeType, $processor)
74
    {
75 2
        $this->overrideOutputProcessor[$mimeType] = $processor;
76 2
        return $this;
77
    }
78
79 2
    public function withDefaultProcessor($processor)
80
    {
81 2
        $this->defaultProcessor = $processor;
82 2
        return $this;
83
    }
84
85
    public function withCache(CacheInterface $cache)
86
    {
87
        $this->cache = $cache;
88
        return $this;
89
    }
90
91 4
    public function getRoutes()
92
    {
93 4
        if (empty($this->routes)) {
94 4
            $routePattern = $this->cache->get('SERVERHANDLERROUTES', false);
95 4
            if ($routePattern === false) {
96 4
                $routePattern = $this->generateRoutes();
97 4
                $this->cache->set('SERVERHANDLERROUTES', $routePattern);
98
            }
99 4
            $this->setRoutes($routePattern);
100
        }
101
102 4
        return parent::getRoutes();
103
    }
104
105
    /**
106
     * @return array
107
     * @throws OperationIdInvalidException
108
     */
109 4
    protected function generateRoutes()
110
    {
111 4
        $basePath = isset($this->schema["basePath"]) ? $this->schema["basePath"] : "";
112 4
        if (empty($basePath) && isset($this->schema["servers"])) {
113 2
            $uri = new Uri($this->schema["servers"][0]["url"]);
114 2
            $basePath = $uri->getPath();
115
        }
116
117 4
        $pathList = $this->sortPaths(array_keys($this->schema['paths']));
118
119 4
        $routes = [];
120 4
        foreach ($pathList as $path) {
121 4
            foreach ($this->schema['paths'][$path] as $method => $properties) {
122 4
                if (!isset($properties['operationId'])) {
123
                    throw new OperationIdInvalidException('OperationId was not found');
124
                }
125
126 4
                $parts = explode('::', $properties['operationId']);
127 4
                if (count($parts) !== 2) {
128
                    throw new OperationIdInvalidException(
129
                        'OperationId needs to be in the format Namespace\\class::method'
130
                    );
131
                }
132
133 4
                $outputProcessor = $this->getMethodOutputProcessor($method, $basePath. $path, $properties);
134
135 4
                $routes[] = new Route(
136 4
                    strtoupper($method),
137 4
                    $basePath . $path,
138
                    $outputProcessor,
139 4
                    $parts[0],
140 4
                    $parts[1]
141
                );
142
            }
143
        }
144
145 4
        return $routes;
146
    }
147
148 5
    protected function sortPaths($pathList)
149
    {
150 5
        usort($pathList, function ($left, $right) {
151 5
            if (strpos($left, '{') === false && strpos($right, '{') !== false) {
152 5
                return -16384;
153
            }
154 5
            if (strpos($left, '{') !== false && strpos($right, '{') === false) {
155 5
                return 16384;
156
            }
157 5
            if (strpos($left, $right) !== false) {
158 1
                return -16384;
159
            }
160 5
            if (strpos($right, $left) !== false) {
161 5
                return 16384;
162
            }
163 5
            return strcmp($left, $right);
164
        });
165
166 5
        return $pathList;
167
    }
168
169
    /**
170
     * @param $method
171
     * @param $path
172
     * @param $properties
173
     * @return string
174
     * @throws OperationIdInvalidException
175
     */
176 4
    protected function getMethodOutputProcessor($method, $path, $properties)
177
    {
178 4
        $key = strtoupper($method) . " " . $path;
179 4
        if (isset($this->overrideOutputProcessor[$key])) {
180 2
            return $this->overrideOutputProcessor[$key];
181
        }
182
183 4
        $produces = null;
184 4
        if (isset($properties['produces'])) {
185 2
            $produces = (array) $properties['produces'];
186
        }
187 4
        if (empty($produces) && isset($properties["responses"]["200"]["content"])) {
188 2
            $produces = array_keys($properties["responses"]["200"]["content"]);
189
        }
190
191 4
        if (empty($produces)) {
192 2
            return $this->defaultProcessor;
193
        }
194
195 4
        $produces = $produces[0];
196
197 4
        if (isset($this->overrideOutputProcessor[$produces])) {
198 2
            return $this->overrideOutputProcessor[$produces];
199
        }
200
201 4
        return BaseOutputProcessor::getFromContentType($produces);
202
    }
203
204
    public function getSchema()
205
    {
206
        return $this->schema;
207
    }
208
}