Schema::getVersion()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * This source file is proprietary and part of Rebilly.
4
 *
5
 * (c) Rebilly SRL
6
 *     Rebilly Ltd.
7
 *     Rebilly Inc.
8
 *
9
 * @see https://www.rebilly.com
10
 */
11
12
namespace Rebilly\OpenAPI;
13
14
use InvalidArgumentException;
15
use JsonSchema\Exception\UnresolvableJsonPointerException;
16
use JsonSchema\SchemaStorage;
17
use JsonSchema\Uri\UriResolver;
18
use JsonSchema\Uri\UriRetriever;
19
use stdClass;
20
21
final class Schema
22
{
23
    private $schemaStorage;
24
25
    private $uri;
26
27 10
    public function __construct(string $uri)
28
    {
29 10
        if (mb_strpos($uri, '//') === false) {
30 10
            $uri = "file://{$uri}";
31
        }
32
33 10
        $schemaStorage = new SchemaStorage(new UriRetriever(), new UriResolver());
34 10
        $schemaStorage->addSchema($uri);
35
36 10
        $this->schemaStorage = $schemaStorage;
37 10
        $this->uri = $uri;
38
39 10
        if (!preg_match('|3\.0(\.\d+)?|', $this->getVersion())) {
40 1
            throw new UnexpectedValueException('Unsupported OpenAPI Specification schema');
41
        }
42
    }
43
44 10
    public function getVersion(): string
45
    {
46 10
        return $this->fetch('#/openapi');
47
    }
48
49 4
    public function getServers(): array
50
    {
51 4
        return array_column($this->fetch('#/servers'), 'url');
52
    }
53
54 2
    public function getDefinition(string $name): stdClass
55
    {
56 2
        return $this->fetch("#/components/schemas/{$name}");
57
    }
58
59 1
    public function getDefinitionNames(): array
60
    {
61 1
        return array_keys((array) $this->fetch('#/components/schemas'));
62
    }
63
64 6
    public function getPathSchema(string $path): stdClass
65
    {
66 6
        return $this->fetch("#/paths/{$this->encodePath($path)}");
67
    }
68
69 1
    public function getAvailablePaths(): array
70
    {
71 1
        return array_keys((array) $this->fetch('#/paths'));
72
    }
73
74 6
    public function getAllowedMethods(string $path): array
75
    {
76 6
        $schema = $this->getPathSchema($path);
77
        $methods = [
78 6
            'OPTIONS' => true,
79 6
            'HEAD' => isset($schema->get),
80 6
            'GET' => isset($schema->get),
81 6
            'POST' => isset($schema->post),
82 6
            'PUT' => isset($schema->put),
83 6
            'DELETE' => isset($schema->delete),
84 6
            'PATCH' => isset($schema->patch),
85
        ];
86
87 6
        return array_keys(array_filter($methods));
88
    }
89
90 3
    public function getRequestHeaderSchemas(string $path, string $method): array
91
    {
92 3
        $parameters = $this->getRequestParameters($path, $method, 'header');
93 3
        $headers = [];
94
95 3
        foreach ($parameters as $parameter) {
96 1
            $headers[] = $parameter->schema;
97
        }
98
99 3
        return $headers;
100
    }
101
102 3
    public function isRequestBodyDefined(string $path, string $method): bool
103
    {
104 3
        return $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/requestBody/content") !== null;
105
    }
106
107 3
    public function getRequestBodySchema(string $path, string $method, string $contentType = null): ?stdClass
108
    {
109 3
        $schemas = $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/requestBody/content");
110
111 3
        if (empty($schemas)) {
112 2
            return null;
113
        }
114
115 2
        if (!$contentType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $contentType of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
116 2
            return reset($schemas)->schema;
117
        }
118
119 1
        if (!isset($schemas->{$contentType})) {
120 1
            throw new InvalidArgumentException('Unsupported request content type');
121
        }
122
123 1
        return $schemas->{$contentType}->schema;
124
    }
125
126 4
    public function getRequestPathParameters(string $path, string $method): array
127
    {
128 4
        return $this->getRequestParameters($path, $method, 'path');
129
    }
130
131 4
    public function getRequestQueryParameters(string $path, string $method): array
132
    {
133 4
        return $this->getRequestParameters($path, $method, 'query');
134
    }
135
136 3
    public function getRequestContentTypes(string $path, string $method): array
137
    {
138 3
        return array_keys((array) $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/requestBody/content"));
139
    }
140
141 4
    public function isResponseDefined(string $path, string $method, string $status): bool
142
    {
143 4
        return $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/responses/{$status}") !== null;
144
    }
145
146 3
    public function isResponseBodyDefined(string $path, string $method, string $status): bool
147
    {
148 3
        return $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/responses/{$status}/content") !== null;
149
    }
150
151 3
    public function getResponseContentTypes(string $path, string $method, string $status): array
152
    {
153 3
        return array_keys((array) $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/responses/{$status}/content"));
154
    }
155
156 4
    public function getResponseHeaderSchemas(string $path, string $method, string $status): array
157
    {
158 4
        $headers = (array) $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/responses/{$status}/headers");
159
160 4
        foreach ($headers as &$header) {
161 4
            if (isset($header->{'$ref'})) {
162 4
                $header = $this->schemaStorage->resolveRef($header->{'$ref'});
163
            }
164
165 4
            if (isset($header->schema->{'$ref'})) {
166 4
                $header->schema = $this->schemaStorage->resolveRef($header->schema->{'$ref'});
167
            }
168
169 4
            $header = $header->schema;
170
        }
171
172 4
        return $headers;
173
    }
174
175 4
    public function getResponseBodySchema(string $path, string $method, string $status, string $contentType = null): ?stdClass
176
    {
177 4
        $schemas = $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/responses/{$status}/content");
178
179 4
        if (empty($schemas)) {
180 1
            return null;
181
        }
182
183 3
        if (!$contentType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $contentType of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
184 3
            return reset($schemas)->schema;
185
        }
186
187 1
        if (!isset($schemas->{$contentType})) {
188 1
            throw new InvalidArgumentException('Unsupported response content type');
189
        }
190
191 1
        return $schemas->{$contentType}->schema;
192
    }
193
194 11
    private function fetch(string $path)
195
    {
196
        try {
197 11
            return $this->schemaStorage->resolveRef(sprintf('%s%s', $this->uri, $path));
198 5
        } catch (UnresolvableJsonPointerException $e) {
199 5
            return null;
200
        }
201
    }
202
203 8
    private function encodePath(string $path): string
204
    {
205 8
        return strtr($path, ['/' => '~1', '~' => '~0', '%' => '%25']);
206
    }
207
208 7
    private function normalizeMethod(string $method): string
209
    {
210 7
        return mb_strtolower($method);
211
    }
212
213 4
    private function getRequestParameters(string $path, string $method, string $location): array
214
    {
215 4
        $operationParameters = $this->normalizeRequestParameters(
216 4
            (array) $this->fetch("#/paths/{$this->encodePath($path)}/{$this->normalizeMethod($method)}/parameters"),
217 4
            $location
218
        );
219
220 4
        $pathParameters = $this->normalizeRequestParameters(
221 4
            (array) $this->fetch("#/paths/{$this->encodePath($path)}/parameters"),
222 4
            $location
223
        );
224
225 4
        return $operationParameters + $pathParameters;
226
    }
227
228 4
    private function normalizeRequestParameters(array $parameters, string $location): array
229
    {
230 4
        $schemas = [];
231
232 4
        foreach ($parameters as &$parameter) {
233 4
            if (isset($parameter->{'$ref'})) {
234 4
                $parameter = $this->schemaStorage->resolveRef($parameter->{'$ref'});
235
            }
236
237 4
            if ($parameter->in !== $location) {
238 4
                continue;
239
            }
240
241 4
            if (isset($parameter->schema->{'$ref'})) {
242 1
                $parameter->schema = $this->schemaStorage->resolveRef($parameter->schema->{'$ref'});
243
            }
244
245 4
            $schemas[$parameter->name] = clone $parameter;
246 4
            unset($schemas[$parameter->name]->name, $schemas[$parameter->name]->in);
247
        }
248
249 4
        return $schemas;
250
    }
251
}
252