1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace ByJG\Swagger; |
4
|
|
|
|
5
|
|
|
use ByJG\Swagger\Exception\DefinitionNotFoundException; |
6
|
|
|
use ByJG\Swagger\Exception\HttpMethodNotFoundException; |
7
|
|
|
use ByJG\Swagger\Exception\InvalidDefinitionException; |
8
|
|
|
use ByJG\Swagger\Exception\NotMatchedException; |
9
|
|
|
use ByJG\Swagger\Exception\PathNotFoundException; |
10
|
|
|
use ByJG\Util\Uri; |
11
|
|
|
|
12
|
|
|
class SwaggerSchema |
13
|
|
|
{ |
14
|
|
|
protected $jsonFile; |
15
|
|
|
protected $allowNullValues; |
16
|
|
|
protected $specificationVersion; |
17
|
|
|
|
18
|
|
|
protected $serverVariables = []; |
19
|
|
|
|
20
|
|
|
const SWAGGER_PATHS = "paths"; |
21
|
|
|
const SWAGGER_PARAMETERS = "parameters"; |
22
|
|
|
const SWAGGER_COMPONENTS = "components"; |
23
|
|
|
|
24
|
|
|
public function __construct($jsonFile, $allowNullValues = false) |
25
|
|
|
{ |
26
|
|
|
$this->jsonFile = json_decode($jsonFile, true); |
27
|
|
|
$this->allowNullValues = (bool) $allowNullValues; |
28
|
|
|
$this->specificationVersion = isset($this->jsonFile['swagger']) ? '2' : '3'; |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Returns the major specification version |
33
|
|
|
* @return string |
34
|
|
|
*/ |
35
|
|
|
public function getSpecificationVersion() |
36
|
|
|
{ |
37
|
|
|
return $this->specificationVersion; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
public function getServerUrl() |
41
|
|
|
{ |
42
|
|
|
if (!isset($this->jsonFile['servers'])) { |
43
|
|
|
return ''; |
44
|
|
|
} |
45
|
|
|
$serverUrl = $this->jsonFile['servers'][0]['url']; |
46
|
|
|
|
47
|
|
|
if (isset($this->jsonFile['servers'][0]['variables'])) { |
48
|
|
|
foreach ($this->jsonFile['servers'][0]['variables'] as $var => $value) { |
49
|
|
|
if (!isset($this->serverVariables[$var])) { |
50
|
|
|
$this->serverVariables[$var] = $value['default']; |
51
|
|
|
} |
52
|
|
|
} |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
foreach ($this->serverVariables as $var => $value) { |
56
|
|
|
$serverUrl = preg_replace("/\{$var\}/", $value, $serverUrl); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return $serverUrl; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
public function getHttpSchema() |
63
|
|
|
{ |
64
|
|
|
return isset($this->jsonFile['schemes']) ? $this->jsonFile['schemes'][0] : ''; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
public function getHost() |
68
|
|
|
{ |
69
|
|
|
return isset($this->jsonFile['host']) ? $this->jsonFile['host'] : ''; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
public function getBasePath() |
73
|
|
|
{ |
74
|
|
|
if ($this->getSpecificationVersion() === '3') { |
75
|
|
|
$uriServer = new Uri($this->getServerUrl()); |
76
|
|
|
return $uriServer->getPath(); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
return isset($this->jsonFile['basePath']) ? $this->jsonFile['basePath'] : ''; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param $path |
84
|
|
|
* @param $method |
85
|
|
|
* @return mixed |
86
|
|
|
* @throws DefinitionNotFoundException |
87
|
|
|
* @throws HttpMethodNotFoundException |
88
|
|
|
* @throws InvalidDefinitionException |
89
|
|
|
* @throws NotMatchedException |
90
|
|
|
* @throws PathNotFoundException |
91
|
|
|
*/ |
92
|
|
|
public function getPathDefinition($path, $method) |
93
|
|
|
{ |
94
|
|
|
$method = strtolower($method); |
95
|
|
|
|
96
|
|
|
$path = preg_replace('~^' . $this->getBasePath() . '~', '', $path); |
97
|
|
|
|
98
|
|
|
$uri = new Uri($path); |
99
|
|
|
|
100
|
|
|
// Try direct match |
101
|
|
|
if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) { |
102
|
|
|
if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) { |
103
|
|
|
return $this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method]; |
104
|
|
|
} |
105
|
|
|
throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'"); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
// Try inline parameter |
109
|
|
|
foreach (array_keys($this->jsonFile[self::SWAGGER_PATHS]) as $pathItem) { |
110
|
|
|
if (strpos($pathItem, '{') === false) { |
111
|
|
|
continue; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$pathItemPattern = '~^' . preg_replace('~{(.*?)}~', '(?<\1>[^/]+)', $pathItem) . '$~'; |
115
|
|
|
|
116
|
|
|
$matches = []; |
117
|
|
|
if (preg_match($pathItemPattern, $uri->getPath(), $matches)) { |
118
|
|
|
$pathDef = $this->jsonFile[self::SWAGGER_PATHS][$pathItem]; |
119
|
|
|
if (!isset($pathDef[$method])) { |
120
|
|
|
throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'"); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$parametersPathMethod = []; |
124
|
|
|
$parametersPath = []; |
125
|
|
|
|
126
|
|
|
if (isset($pathDef[$method][self::SWAGGER_PARAMETERS])) { |
127
|
|
|
$parametersPathMethod = $pathDef[$method][self::SWAGGER_PARAMETERS]; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
if (isset($pathDef[self::SWAGGER_PARAMETERS])) { |
131
|
|
|
$parametersPath = $pathDef[self::SWAGGER_PARAMETERS]; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
$this->validateArguments('path', array_merge($parametersPathMethod, $parametersPath), $matches); |
135
|
|
|
|
136
|
|
|
return $pathDef[$method]; |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
throw new PathNotFoundException('Path "' . $path . '" not found'); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @param $parameterIn |
145
|
|
|
* @param $parameters |
146
|
|
|
* @param $arguments |
147
|
|
|
* @throws DefinitionNotFoundException |
148
|
|
|
* @throws InvalidDefinitionException |
149
|
|
|
* @throws NotMatchedException |
150
|
|
|
*/ |
151
|
|
|
private function validateArguments($parameterIn, $parameters, $arguments) |
152
|
|
|
{ |
153
|
|
|
if ($this->getSpecificationVersion() === '3') { |
154
|
|
|
foreach ($parameters as $parameter) { |
155
|
|
|
if (isset($parameter['$ref'])) { |
156
|
|
|
$paramParts = explode("/", $parameter['$ref']); |
157
|
|
|
if (count($paramParts) != 4 || $paramParts[0] != "#" || $paramParts[1] != self::SWAGGER_COMPONENTS || $paramParts[2] != self::SWAGGER_PARAMETERS) { |
158
|
|
|
throw new InvalidDefinitionException( |
159
|
|
|
"Not get the reference in the expected format #/components/parameters/<NAME>" |
160
|
|
|
); |
161
|
|
|
} |
162
|
|
|
if (!isset($this->jsonFile[self::SWAGGER_COMPONENTS][self::SWAGGER_PARAMETERS][$paramParts[3]])) { |
163
|
|
|
throw new DefinitionNotFoundException( |
164
|
|
|
"Not find reference #/components/parameters/${paramParts[3]}" |
165
|
|
|
); |
166
|
|
|
} |
167
|
|
|
$parameter = $this->jsonFile[self::SWAGGER_COMPONENTS][self::SWAGGER_PARAMETERS][$paramParts[3]]; |
168
|
|
|
} |
169
|
|
View Code Duplication |
if ($parameter['in'] === $parameterIn && |
|
|
|
|
170
|
|
|
$parameter['schema']['type'] === "integer" |
171
|
|
|
&& filter_var($arguments[$parameter['name']], FILTER_VALIDATE_INT) === false) { |
172
|
|
|
throw new NotMatchedException('Path expected an integer value'); |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
return; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
foreach ($parameters as $parameter) { |
179
|
|
View Code Duplication |
if ($parameter['in'] === $parameterIn |
|
|
|
|
180
|
|
|
&& $parameter['type'] === "integer" |
181
|
|
|
&& filter_var($arguments[$parameter['name']], FILTER_VALIDATE_INT) === false) { |
182
|
|
|
throw new NotMatchedException('Path expected an integer value'); |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param $name |
189
|
|
|
* @return mixed |
190
|
|
|
* @throws DefinitionNotFoundException |
191
|
|
|
* @throws InvalidDefinitionException |
192
|
|
|
*/ |
193
|
|
|
public function getDefintion($name) |
194
|
|
|
{ |
195
|
|
|
$nameParts = explode('/', $name); |
196
|
|
|
|
197
|
|
|
if ($this->getSpecificationVersion() === '3') { |
198
|
|
View Code Duplication |
if (count($nameParts) < 4 || $nameParts[0] !== '#') { |
|
|
|
|
199
|
|
|
throw new InvalidDefinitionException('Invalid Component'); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
View Code Duplication |
if (!isset($this->jsonFile[$nameParts[1]][$nameParts[2]][$nameParts[3]])) { |
|
|
|
|
203
|
|
|
throw new DefinitionNotFoundException("Component'$name' not found"); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
return $this->jsonFile[$nameParts[1]][$nameParts[2]][$nameParts[3]]; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
View Code Duplication |
if (count($nameParts) < 3 || $nameParts[0] !== '#') { |
|
|
|
|
210
|
|
|
throw new InvalidDefinitionException('Invalid Definition'); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
View Code Duplication |
if (!isset($this->jsonFile[$nameParts[1]][$nameParts[2]])) { |
|
|
|
|
214
|
|
|
throw new DefinitionNotFoundException("Definition '$name' not found"); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $this->jsonFile[$nameParts[1]][$nameParts[2]]; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* @param $path |
222
|
|
|
* @param $method |
223
|
|
|
* @return SwaggerRequestBody |
224
|
|
|
* @throws DefinitionNotFoundException |
225
|
|
|
* @throws HttpMethodNotFoundException |
226
|
|
|
* @throws InvalidDefinitionException |
227
|
|
|
* @throws NotMatchedException |
228
|
|
|
* @throws PathNotFoundException |
229
|
|
|
*/ |
230
|
|
|
public function getRequestParameters($path, $method) |
231
|
|
|
{ |
232
|
|
|
$structure = $this->getPathDefinition($path, $method); |
233
|
|
|
|
234
|
|
|
if($this->getSpecificationVersion() === '3') { |
235
|
|
|
if (!isset($structure['requestBody'])) { |
236
|
|
|
return new SwaggerRequestBody($this, "$method $path", []); |
237
|
|
|
} |
238
|
|
|
return new SwaggerRequestBody($this, "$method $path", $structure['requestBody']); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
if (!isset($structure[self::SWAGGER_PARAMETERS])) { |
242
|
|
|
return new SwaggerRequestBody($this, "$method $path", []); |
243
|
|
|
} |
244
|
|
|
return new SwaggerRequestBody($this, "$method $path", $structure[self::SWAGGER_PARAMETERS]); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* @param $path |
249
|
|
|
* @param $method |
250
|
|
|
* @param $status |
251
|
|
|
* @return SwaggerResponseBody |
252
|
|
|
* @throws HttpMethodNotFoundException |
253
|
|
|
* @throws InvalidDefinitionException |
254
|
|
|
* @throws NotMatchedException |
255
|
|
|
* @throws PathNotFoundException |
256
|
|
|
* @throws DefinitionNotFoundException |
257
|
|
|
*/ |
258
|
|
|
public function getResponseParameters($path, $method, $status) |
259
|
|
|
{ |
260
|
|
|
$structure = $this->getPathDefinition($path, $method); |
261
|
|
|
|
262
|
|
|
if (!isset($structure['responses'][$status])) { |
263
|
|
|
throw new InvalidDefinitionException("Could not found status code '$status' in '$path' and '$method'"); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
return new SwaggerResponseBody($this, "$method $status $path", $structure['responses'][$status]); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* OpenApi 2.0 doesn't describe null values, so this flag defines, |
271
|
|
|
* if match is ok when one of property |
272
|
|
|
* |
273
|
|
|
* @return bool |
274
|
|
|
*/ |
275
|
|
|
public function isAllowNullValues() |
276
|
|
|
{ |
277
|
|
|
return $this->allowNullValues; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* OpenApi 2.0 doesn't describe null values, so this flag defines, |
282
|
|
|
* if match is ok when one of property |
283
|
|
|
* |
284
|
|
|
* @param $value |
285
|
|
|
*/ |
286
|
|
|
public function setAllowNullValues($value) |
287
|
|
|
{ |
288
|
|
|
$this->allowNullValues = (bool) $value; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
public function setServerVariable($var, $value) |
292
|
|
|
{ |
293
|
|
|
$this->serverVariables[$var] = $value; |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.