1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Swaggest\JsonSchema; |
4
|
|
|
|
5
|
|
|
|
6
|
|
|
use PhpLang\ScopeExit; |
7
|
|
|
use Swaggest\JsonDiff\JsonDiff; |
8
|
|
|
use Swaggest\JsonSchema\Constraint\Content; |
9
|
|
|
use Swaggest\JsonSchema\Constraint\Format; |
10
|
|
|
use Swaggest\JsonSchema\Constraint\Properties; |
11
|
|
|
use Swaggest\JsonSchema\Constraint\Type; |
12
|
|
|
use Swaggest\JsonSchema\Constraint\UniqueItems; |
13
|
|
|
use Swaggest\JsonSchema\Exception\ArrayException; |
14
|
|
|
use Swaggest\JsonSchema\Exception\ConstException; |
15
|
|
|
use Swaggest\JsonSchema\Exception\EnumException; |
16
|
|
|
use Swaggest\JsonSchema\Exception\LogicException; |
17
|
|
|
use Swaggest\JsonSchema\Exception\NumericException; |
18
|
|
|
use Swaggest\JsonSchema\Exception\ObjectException; |
19
|
|
|
use Swaggest\JsonSchema\Exception\StringException; |
20
|
|
|
use Swaggest\JsonSchema\Exception\TypeException; |
21
|
|
|
use Swaggest\JsonSchema\Meta\Meta; |
22
|
|
|
use Swaggest\JsonSchema\Meta\MetaHolder; |
23
|
|
|
use Swaggest\JsonSchema\Structure\ClassStructure; |
24
|
|
|
use Swaggest\JsonSchema\Structure\Egg; |
25
|
|
|
use Swaggest\JsonSchema\Structure\ObjectItem; |
26
|
|
|
use Swaggest\JsonSchema\Structure\ObjectItemContract; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Class Schema |
30
|
|
|
* @package Swaggest\JsonSchema |
31
|
|
|
*/ |
32
|
|
|
class Schema extends JsonSchema implements MetaHolder |
33
|
|
|
{ |
34
|
|
|
const CONST_PROPERTY = 'const'; |
35
|
|
|
|
36
|
|
|
const DEFAULT_MAPPING = 'default'; |
37
|
|
|
|
38
|
|
|
const VERSION_AUTO = 'a'; |
39
|
|
|
const VERSION_DRAFT_04 = 4; |
40
|
|
|
const VERSION_DRAFT_06 = 6; |
41
|
|
|
const VERSION_DRAFT_07 = 7; |
42
|
|
|
|
43
|
|
|
const REF = '$ref'; |
44
|
|
|
const ID = '$id'; |
45
|
|
|
const ID_D4 = 'id'; |
46
|
|
|
|
47
|
|
|
// Object |
48
|
|
|
/** @var Properties|Schema[]|Schema */ |
49
|
|
|
public $properties; |
50
|
|
|
/** @var Schema|bool */ |
51
|
|
|
public $additionalProperties; |
52
|
|
|
/** @var Schema[] */ |
53
|
|
|
public $patternProperties; |
54
|
|
|
/** @var string[][]|Schema[]|\stdClass */ |
55
|
|
|
public $dependencies; |
56
|
|
|
|
57
|
|
|
// Array |
58
|
|
|
/** @var null|Schema|Schema[] */ |
59
|
|
|
public $items; |
60
|
|
|
/** @var null|Schema|bool */ |
61
|
|
|
public $additionalItems; |
62
|
|
|
|
63
|
|
|
/** @var Schema[] */ |
64
|
|
|
public $allOf; |
65
|
|
|
/** @var Schema */ |
66
|
|
|
public $not; |
67
|
|
|
/** @var Schema[] */ |
68
|
|
|
public $anyOf; |
69
|
|
|
/** @var Schema[] */ |
70
|
|
|
public $oneOf; |
71
|
|
|
|
72
|
|
|
/** @var Schema */ |
73
|
|
|
public $if; |
74
|
|
|
/** @var Schema */ |
75
|
|
|
public $then; |
76
|
|
|
/** @var Schema */ |
77
|
|
|
public $else; |
78
|
|
|
|
79
|
|
|
|
80
|
|
|
public $objectItemClass; |
81
|
|
|
private $useObjectAsArray = false; |
82
|
|
|
|
83
|
|
|
private $__dataToProperty = array(); |
84
|
|
|
private $__propertyToData = array(); |
85
|
|
|
|
86
|
|
|
private $__booleanSchema; |
87
|
|
|
|
88
|
3 |
|
public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING) |
89
|
|
|
{ |
90
|
3 |
|
$this->__dataToProperty[$mapping][$dataName] = $propertyName; |
91
|
3 |
|
$this->__propertyToData[$mapping][$propertyName] = $dataName; |
92
|
3 |
|
return $this; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @param mixed $data |
97
|
|
|
* @param Context|null $options |
98
|
|
|
* @return static|JsonSchema |
99
|
|
|
* @throws Exception |
100
|
|
|
* @throws InvalidValue |
101
|
|
|
* @throws \Exception |
102
|
|
|
*/ |
103
|
3171 |
|
public static function import($data, Context $options = null) |
104
|
|
|
{ |
105
|
3171 |
|
if (null === $options) { |
106
|
21 |
|
$options = new Context(); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
//$options->applyDefaults = false; // todo check infinite recursion on items, additionalProperties, etc... |
|
|
|
|
110
|
|
|
|
111
|
3171 |
|
if (isset($options->schemasCache) && is_object($data)) { |
112
|
|
|
if ($options->schemasCache->contains($data)) { |
113
|
|
|
return $options->schemasCache->offsetGet($data); |
114
|
|
|
} else { |
115
|
|
|
$schema = parent::import($data, $options); |
116
|
|
|
$options->schemasCache->attach($data, $schema); |
117
|
|
|
return $schema; |
|
|
|
|
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
// string $data is expected to be $ref uri |
122
|
3171 |
|
if (is_string($data)) { |
123
|
4 |
|
$data = (object)array(self::REF => $data); |
124
|
|
|
} |
125
|
|
|
|
126
|
3171 |
|
$data = self::unboolSchema($data); |
127
|
3171 |
|
if ($data instanceof Schema) { |
128
|
72 |
|
return $data; |
|
|
|
|
129
|
|
|
} |
130
|
|
|
|
131
|
3099 |
|
return parent::import($data, $options); |
|
|
|
|
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @param mixed $data |
136
|
|
|
* @param Context|null $options |
137
|
|
|
* @return array|mixed|null|object|\stdClass |
138
|
|
|
* @throws Exception |
139
|
|
|
* @throws InvalidValue |
140
|
|
|
* @throws \Exception |
141
|
|
|
*/ |
142
|
3191 |
|
public function in($data, Context $options = null) |
143
|
|
|
{ |
144
|
3191 |
|
if (null !== $this->__booleanSchema) { |
145
|
72 |
|
if ($this->__booleanSchema) { |
146
|
36 |
|
return $data; |
147
|
36 |
|
} elseif (empty($options->skipValidation)) { |
148
|
18 |
|
$this->fail(new InvalidValue('Denied by false schema'), '#'); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
3137 |
|
if ($options === null) { |
153
|
45 |
|
$options = new Context(); |
154
|
|
|
} |
155
|
|
|
|
156
|
3137 |
|
$options->import = true; |
157
|
|
|
|
158
|
3137 |
|
if ($options->refResolver === null) { |
159
|
2818 |
|
$options->refResolver = new RefResolver($data); |
160
|
|
|
} else { |
161
|
1693 |
|
$options->refResolver->setRootData($data); |
162
|
|
|
} |
163
|
|
|
|
164
|
3137 |
|
if ($options->remoteRefProvider) { |
165
|
3077 |
|
$options->refResolver->setRemoteRefProvider($options->remoteRefProvider); |
166
|
|
|
} |
167
|
|
|
|
168
|
3137 |
|
if ($options->import) { |
169
|
3137 |
|
$options->refResolver->preProcessReferences($data, $options); |
170
|
|
|
} |
171
|
|
|
|
172
|
3137 |
|
return $this->process($data, $options, '#'); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @param mixed $data |
178
|
|
|
* @param Context|null $options |
179
|
|
|
* @return array|mixed|null|object|\stdClass |
180
|
|
|
* @throws InvalidValue |
181
|
|
|
* @throws \Exception |
182
|
|
|
*/ |
183
|
2360 |
|
public function out($data, Context $options = null) |
184
|
|
|
{ |
185
|
2360 |
|
if ($options === null) { |
186
|
951 |
|
$options = new Context(); |
187
|
|
|
} |
188
|
|
|
|
189
|
2360 |
|
$options->circularReferences = new \SplObjectStorage(); |
190
|
2360 |
|
$options->import = false; |
191
|
2360 |
|
return $this->process($data, $options); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param mixed $data |
196
|
|
|
* @param Context $options |
197
|
|
|
* @param string $path |
198
|
|
|
* @throws InvalidValue |
199
|
|
|
* @throws \Exception |
200
|
|
|
*/ |
201
|
3119 |
|
private function processType($data, Context $options, $path = '#') |
202
|
|
|
{ |
203
|
3119 |
|
if ($options->tolerateStrings && is_string($data)) { |
204
|
|
|
$valid = Type::readString($this->type, $data); |
205
|
|
|
} else { |
206
|
3119 |
|
$valid = Type::isValid($this->type, $data, $options->version); |
207
|
|
|
} |
208
|
3119 |
|
if (!$valid) { |
209
|
673 |
|
$this->fail(new TypeException(ucfirst( |
210
|
673 |
|
implode(', ', is_array($this->type) ? $this->type : array($this->type)) |
211
|
673 |
|
. ' expected, ' . json_encode($data) . ' received') |
212
|
673 |
|
), $path); |
213
|
|
|
} |
214
|
3117 |
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* @param mixed $data |
218
|
|
|
* @param string $path |
219
|
|
|
* @throws InvalidValue |
220
|
|
|
* @throws \Exception |
221
|
|
|
*/ |
222
|
1457 |
|
private function processEnum($data, $path = '#') |
223
|
|
|
{ |
224
|
1457 |
|
$enumOk = false; |
225
|
1457 |
|
foreach ($this->enum as $item) { |
226
|
1457 |
|
if ($item === $data) { // todo support complex structures here |
227
|
1443 |
|
$enumOk = true; |
228
|
1457 |
|
break; |
229
|
|
|
} |
230
|
|
|
} |
231
|
1457 |
|
if (!$enumOk) { |
232
|
89 |
|
$this->fail(new EnumException('Enum failed'), $path); |
233
|
|
|
} |
234
|
1443 |
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @param mixed $data |
238
|
|
|
* @param string $path |
239
|
|
|
* @throws InvalidValue |
240
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
241
|
|
|
*/ |
242
|
43 |
|
private function processConst($data, $path) |
243
|
|
|
{ |
244
|
43 |
|
if ($this->const !== $data) { |
245
|
37 |
|
if ((is_object($this->const) && is_object($data)) |
246
|
37 |
|
|| (is_array($this->const) && is_array($data))) { |
247
|
15 |
|
$diff = new JsonDiff($this->const, $data, |
248
|
15 |
|
JsonDiff::STOP_ON_DIFF); |
249
|
15 |
|
if ($diff->getDiffCnt() != 0) { |
250
|
15 |
|
$this->fail(new ConstException('Const failed'), $path); |
251
|
|
|
} |
252
|
|
|
} else { |
253
|
22 |
|
$this->fail(new ConstException('Const failed'), $path); |
254
|
|
|
} |
255
|
|
|
} |
256
|
20 |
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* @param mixed $data |
260
|
|
|
* @param Context $options |
261
|
|
|
* @param string $path |
262
|
|
|
* @throws InvalidValue |
263
|
|
|
* @throws \Exception |
264
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
265
|
|
|
*/ |
266
|
68 |
|
private function processNot($data, Context $options, $path) |
267
|
|
|
{ |
268
|
68 |
|
$exception = false; |
269
|
|
|
try { |
270
|
68 |
|
self::unboolSchema($this->not)->process($data, $options, $path . '->not'); |
271
|
15 |
|
} catch (InvalidValue $exception) { |
272
|
|
|
// Expected exception |
273
|
|
|
} |
274
|
68 |
|
if ($exception === false) { |
275
|
55 |
|
$this->fail(new LogicException('Failed due to logical constraint: not'), $path); |
276
|
|
|
} |
277
|
15 |
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @param string $data |
281
|
|
|
* @param string $path |
282
|
|
|
* @throws InvalidValue |
283
|
|
|
*/ |
284
|
2227 |
|
private function processString($data, $path) |
285
|
|
|
{ |
286
|
2227 |
|
if ($this->minLength !== null) { |
287
|
38 |
|
if (mb_strlen($data, 'UTF-8') < $this->minLength) { |
288
|
9 |
|
$this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path); |
289
|
|
|
} |
290
|
|
|
} |
291
|
2221 |
|
if ($this->maxLength !== null) { |
292
|
43 |
|
if (mb_strlen($data, 'UTF-8') > $this->maxLength) { |
293
|
19 |
|
$this->fail(new StringException('String is too long', StringException::TOO_LONG), $path); |
294
|
|
|
} |
295
|
|
|
} |
296
|
2218 |
|
if ($this->pattern !== null) { |
297
|
17 |
|
if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) { |
298
|
4 |
|
$this->fail(new StringException(json_encode($data) . ' does not match to ' |
299
|
4 |
|
. $this->pattern, StringException::PATTERN_MISMATCH), $path); |
300
|
|
|
} |
301
|
|
|
} |
302
|
2218 |
|
if ($this->format !== null) { |
303
|
401 |
|
$validationError = Format::validationError($this->format, $data); |
304
|
401 |
|
if ($validationError !== null) { |
305
|
137 |
|
if ($this->format === "uri" && substr($path, -3) === ':id') { |
|
|
|
|
306
|
|
|
} else { |
307
|
122 |
|
$this->fail(new StringException($validationError), $path); |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
} |
311
|
2218 |
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* @param float|int $data |
315
|
|
|
* @param string $path |
316
|
|
|
* @throws InvalidValue |
317
|
|
|
*/ |
318
|
1037 |
|
private function processNumeric($data, $path) |
319
|
|
|
{ |
320
|
1037 |
|
if ($this->multipleOf !== null) { |
321
|
39 |
|
$div = $data / $this->multipleOf; |
322
|
39 |
|
if ($div != (int)$div) { |
323
|
15 |
|
$this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path); |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
|
327
|
1037 |
|
if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) { |
328
|
32 |
|
if ($data >= $this->exclusiveMaximum) { |
329
|
18 |
|
$this->fail(new NumericException( |
330
|
18 |
|
'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received', |
331
|
18 |
|
NumericException::MAXIMUM), $path); |
332
|
|
|
} |
333
|
|
|
} |
334
|
|
|
|
335
|
1037 |
|
if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) { |
336
|
24 |
|
if ($data <= $this->exclusiveMinimum) { |
337
|
12 |
|
$this->fail(new NumericException( |
338
|
12 |
|
'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received', |
339
|
12 |
|
NumericException::MINIMUM), $path); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
1037 |
|
if ($this->maximum !== null) { |
344
|
43 |
|
if ($this->exclusiveMaximum === true) { |
345
|
3 |
|
if ($data >= $this->maximum) { |
346
|
2 |
|
$this->fail(new NumericException( |
347
|
2 |
|
'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received', |
348
|
3 |
|
NumericException::MAXIMUM), $path); |
349
|
|
|
} |
350
|
|
|
} else { |
351
|
40 |
|
if ($data > $this->maximum) { |
352
|
13 |
|
$this->fail(new NumericException( |
353
|
13 |
|
'Value less than ' . $this->minimum . ' expected, ' . $data . ' received', |
354
|
13 |
|
NumericException::MAXIMUM), $path); |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
359
|
1037 |
|
if ($this->minimum !== null) { |
360
|
515 |
|
if ($this->exclusiveMinimum === true) { |
361
|
93 |
|
if ($data <= $this->minimum) { |
362
|
2 |
|
$this->fail(new NumericException( |
363
|
2 |
|
'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received', |
364
|
93 |
|
NumericException::MINIMUM), $path); |
365
|
|
|
} |
366
|
|
|
} else { |
367
|
440 |
|
if ($data < $this->minimum) { |
368
|
43 |
|
$this->fail(new NumericException( |
369
|
43 |
|
'Value more than ' . $this->minimum . ' expected, ' . $data . ' received', |
370
|
43 |
|
NumericException::MINIMUM), $path); |
371
|
|
|
} |
372
|
|
|
} |
373
|
|
|
} |
374
|
1036 |
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* @param mixed $data |
378
|
|
|
* @param Context $options |
379
|
|
|
* @param string $path |
380
|
|
|
* @return array|mixed|null|object|\stdClass |
381
|
|
|
* @throws InvalidValue |
382
|
|
|
* @throws \Exception |
383
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
384
|
|
|
*/ |
385
|
99 |
|
private function processOneOf($data, Context $options, $path) |
386
|
|
|
{ |
387
|
99 |
|
$successes = 0; |
388
|
99 |
|
$failures = ''; |
389
|
99 |
|
$skipValidation = false; |
390
|
99 |
|
if ($options->skipValidation) { |
391
|
41 |
|
$skipValidation = true; |
392
|
41 |
|
$options->skipValidation = false; |
393
|
|
|
} |
394
|
|
|
|
395
|
99 |
|
$result = $data; |
396
|
99 |
|
foreach ($this->oneOf as $index => $item) { |
397
|
|
|
try { |
398
|
99 |
|
$result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf:' . $index); |
399
|
78 |
|
$successes++; |
400
|
78 |
|
if ($successes > 1 || $options->skipValidation) { |
401
|
78 |
|
break; |
402
|
|
|
} |
403
|
67 |
|
} catch (InvalidValue $exception) { |
404
|
99 |
|
$failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n"; |
405
|
|
|
// Expected exception |
406
|
|
|
} |
407
|
|
|
} |
408
|
99 |
|
if ($skipValidation) { |
409
|
41 |
|
$options->skipValidation = true; |
410
|
41 |
|
if ($successes === 0) { |
411
|
8 |
|
$result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf:' . 0); |
412
|
|
|
} |
413
|
|
|
} |
414
|
|
|
|
415
|
99 |
|
if (!$options->skipValidation) { |
416
|
58 |
|
if ($successes === 0) { |
417
|
13 |
|
$this->fail(new LogicException('Failed due to logical constraint: no valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path); |
418
|
45 |
|
} elseif ($successes > 1) { |
419
|
16 |
|
$this->fail(new LogicException('Failed due to logical constraint: ' |
420
|
16 |
|
. $successes . '/' . count($this->oneOf) . ' valid results for oneOf'), $path); |
421
|
|
|
} |
422
|
|
|
} |
423
|
70 |
|
return $result; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* @param mixed $data |
428
|
|
|
* @param Context $options |
429
|
|
|
* @param string $path |
430
|
|
|
* @return array|mixed|null|object|\stdClass |
431
|
|
|
* @throws InvalidValue |
432
|
|
|
* @throws \Exception |
433
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
434
|
|
|
*/ |
435
|
1703 |
|
private function processAnyOf($data, Context $options, $path) |
436
|
|
|
{ |
437
|
1703 |
|
$successes = 0; |
438
|
1703 |
|
$failures = ''; |
439
|
1703 |
|
$result = $data; |
440
|
1703 |
|
foreach ($this->anyOf as $index => $item) { |
441
|
|
|
try { |
442
|
1703 |
|
$result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf:' . $index); |
443
|
1697 |
|
$successes++; |
444
|
1697 |
|
if ($successes) { |
445
|
1697 |
|
break; |
446
|
|
|
} |
447
|
428 |
|
} catch (InvalidValue $exception) { |
448
|
428 |
|
$failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n"; |
449
|
|
|
// Expected exception |
450
|
|
|
} |
451
|
|
|
} |
452
|
1703 |
|
if (!$successes && !$options->skipValidation) { |
453
|
28 |
|
$this->fail(new LogicException('Failed due to logical constraint: no valid results for anyOf {' . "\n" . substr(Helper::padLines(' ', $failures), 0, -1) . "\n}"), $path); |
454
|
|
|
} |
455
|
1697 |
|
return $result; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* @param mixed $data |
460
|
|
|
* @param Context $options |
461
|
|
|
* @param string $path |
462
|
|
|
* @return array|mixed|null|object|\stdClass |
463
|
|
|
* @throws InvalidValue |
464
|
|
|
* @throws \Exception |
465
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
466
|
|
|
*/ |
467
|
351 |
|
private function processAllOf($data, Context $options, $path) |
468
|
|
|
{ |
469
|
351 |
|
$result = $data; |
470
|
351 |
|
foreach ($this->allOf as $index => $item) { |
471
|
351 |
|
$result = self::unboolSchema($item)->process($data, $options, $path . '->allOf' . $index); |
472
|
|
|
} |
473
|
313 |
|
return $result; |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* @param mixed $data |
478
|
|
|
* @param Context $options |
479
|
|
|
* @param string $path |
480
|
|
|
* @return array|mixed|null|object|\stdClass |
481
|
|
|
* @throws InvalidValue |
482
|
|
|
* @throws \Exception |
483
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
484
|
|
|
*/ |
485
|
26 |
|
private function processIf($data, Context $options, $path) |
486
|
|
|
{ |
487
|
26 |
|
$valid = true; |
488
|
|
|
try { |
489
|
26 |
|
self::unboolSchema($this->if)->process($data, $options, $path . '->if'); |
490
|
13 |
|
} catch (InvalidValue $exception) { |
491
|
13 |
|
$valid = false; |
492
|
|
|
} |
493
|
26 |
|
if ($valid) { |
494
|
18 |
|
if ($this->then !== null) { |
495
|
18 |
|
return self::unboolSchema($this->then)->process($data, $options, $path . '->then'); |
496
|
|
|
} |
497
|
|
|
} else { |
498
|
13 |
|
if ($this->else !== null) { |
499
|
6 |
|
return self::unboolSchema($this->else)->process($data, $options, $path . '->else'); |
500
|
|
|
} |
501
|
|
|
} |
502
|
10 |
|
return null; |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
/** |
506
|
|
|
* @param \stdClass $data |
507
|
|
|
* @param Context $options |
508
|
|
|
* @param string $path |
509
|
|
|
* @throws InvalidValue |
510
|
|
|
*/ |
511
|
159 |
|
private function processObjectRequired($data, Context $options, $path) |
512
|
|
|
{ |
513
|
159 |
|
if (isset($this->__dataToProperty[$options->mapping])) { |
514
|
2 |
|
if ($options->import) { |
515
|
1 |
|
foreach ($this->required as $item) { |
516
|
1 |
|
if (isset($this->__propertyToData[$options->mapping][$item])) { |
517
|
1 |
|
$item = $this->__propertyToData[$options->mapping][$item]; |
518
|
|
|
} |
519
|
1 |
|
if (!property_exists($data, $item)) { |
520
|
1 |
|
$this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path); |
521
|
|
|
} |
522
|
|
|
} |
523
|
|
|
} else { |
524
|
2 |
|
foreach ($this->required as $item) { |
525
|
2 |
|
if (isset($this->__dataToProperty[$options->mapping][$item])) { |
526
|
|
|
$item = $this->__dataToProperty[$options->mapping][$item]; |
527
|
|
|
} |
528
|
2 |
|
if (!property_exists($data, $item)) { |
529
|
2 |
|
$this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path); |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
} else { |
535
|
158 |
|
foreach ($this->required as $item) { |
536
|
154 |
|
if (!property_exists($data, $item)) { |
537
|
154 |
|
$this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path); |
538
|
|
|
} |
539
|
|
|
} |
540
|
|
|
} |
541
|
127 |
|
} |
542
|
|
|
|
543
|
|
|
/** |
544
|
|
|
* @param \stdClass $data |
545
|
|
|
* @param Context $options |
546
|
|
|
* @param string $path |
547
|
|
|
* @param ObjectItemContract|null $result |
548
|
|
|
* @return array|null|ClassStructure|ObjectItemContract |
549
|
|
|
* @throws InvalidValue |
550
|
|
|
* @throws \Exception |
551
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
552
|
|
|
*/ |
553
|
3132 |
|
private function processObject($data, Context $options, $path, $result = null) |
554
|
|
|
{ |
555
|
3132 |
|
$import = $options->import; |
556
|
|
|
|
557
|
3132 |
|
if (!$options->skipValidation && $this->required !== null) { |
558
|
159 |
|
$this->processObjectRequired($data, $options, $path); |
559
|
|
|
} |
560
|
|
|
|
561
|
3131 |
|
if ($import) { |
562
|
3119 |
|
if (!$options->validateOnly) { |
563
|
|
|
|
564
|
3119 |
|
if ($this->useObjectAsArray) { |
565
|
1 |
|
$result = array(); |
566
|
3118 |
|
} elseif (!$result instanceof ObjectItemContract) { |
567
|
|
|
//$result = $this->makeObjectItem($options); |
|
|
|
|
568
|
|
|
//* todo check performance impact |
569
|
3118 |
|
if (null === $this->objectItemClass) { |
570
|
1148 |
|
$result = new ObjectItem(); |
571
|
|
|
} else { |
572
|
3108 |
|
$className = $this->objectItemClass; |
573
|
3108 |
|
if ($options->objectItemClassMapping !== null) { |
574
|
|
|
if (isset($options->objectItemClassMapping[$className])) { |
575
|
|
|
$className = $options->objectItemClassMapping[$className]; |
576
|
|
|
} |
577
|
|
|
} |
578
|
3108 |
|
$result = new $className; |
579
|
|
|
} |
580
|
|
|
//*/ |
581
|
|
|
|
582
|
|
|
|
583
|
3118 |
|
if ($result instanceof ClassStructure) { |
584
|
3107 |
|
if ($result->__validateOnSet) { |
585
|
3107 |
|
$result->__validateOnSet = false; |
586
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */ |
587
|
|
|
/* todo check performance impact |
|
|
|
|
588
|
|
|
$validateOnSetHandler = new ScopeExit(function () use ($result) { |
589
|
|
|
$result->__validateOnSet = true; |
590
|
|
|
}); |
591
|
|
|
//*/ |
592
|
|
|
} |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
//* todo check performance impact |
596
|
3118 |
|
if ($result instanceof ObjectItemContract) { |
597
|
3118 |
|
$result->setDocumentPath($path); |
598
|
|
|
} |
599
|
|
|
//*/ |
600
|
|
|
} |
601
|
|
|
} |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
// @todo better check for schema id |
605
|
|
|
|
606
|
3131 |
|
if ($import |
607
|
3131 |
|
&& isset($data->{Schema::ID_D4}) |
608
|
3131 |
|
&& ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO) |
609
|
3131 |
|
&& is_string($data->{Schema::ID_D4}) /*&& (!isset($this->properties['id']))/* && $this->isMetaSchema($data)*/) { |
|
|
|
|
610
|
29 |
|
$id = $data->{Schema::ID_D4}; |
611
|
29 |
|
$refResolver = $options->refResolver; |
612
|
29 |
|
$parentScope = $refResolver->updateResolutionScope($id); |
613
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */ |
614
|
|
|
$defer = new ScopeExit(function () use ($parentScope, $refResolver) { |
615
|
29 |
|
$refResolver->setResolutionScope($parentScope); |
616
|
29 |
|
}); |
617
|
|
|
} |
618
|
|
|
|
619
|
3131 |
|
if ($import |
620
|
3131 |
|
&& isset($data->{self::ID}) |
621
|
3131 |
|
&& ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO) |
622
|
3131 |
|
&& is_string($data->{self::ID}) /*&& (!isset($this->properties['id']))/* && $this->isMetaSchema($data)*/) { |
|
|
|
|
623
|
114 |
|
$id = $data->{self::ID}; |
624
|
114 |
|
$refResolver = $options->refResolver; |
625
|
114 |
|
$parentScope = $refResolver->updateResolutionScope($id); |
626
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */ |
627
|
|
|
$defer = new ScopeExit(function () use ($parentScope, $refResolver) { |
628
|
114 |
|
$refResolver->setResolutionScope($parentScope); |
629
|
114 |
|
}); |
630
|
|
|
} |
631
|
|
|
|
632
|
3131 |
|
if ($import) { |
633
|
|
|
try { |
634
|
|
|
while ( |
635
|
3119 |
|
isset($data->{self::REF}) |
636
|
3119 |
|
&& is_string($data->{self::REF}) |
637
|
3119 |
|
&& !isset($this->properties[self::REF]) |
638
|
|
|
) { |
639
|
407 |
|
$refString = $data->{self::REF}; |
640
|
|
|
|
641
|
|
|
// todo check performance impact |
642
|
407 |
|
if ($refString === 'http://json-schema.org/draft-04/schema#' |
643
|
397 |
|
|| $refString === 'http://json-schema.org/draft-06/schema#' |
644
|
407 |
|
|| $refString === 'http://json-schema.org/draft-07/schema#') { |
645
|
26 |
|
return Schema::schema(); |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
// TODO consider process # by reference here ? |
649
|
381 |
|
$refResolver = $options->refResolver; |
650
|
381 |
|
$preRefScope = $refResolver->getResolutionScope(); |
651
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */ |
652
|
381 |
|
$deferRefScope = new ScopeExit(function () use ($preRefScope, $refResolver) { |
653
|
381 |
|
$refResolver->setResolutionScope($preRefScope); |
654
|
381 |
|
}); |
655
|
|
|
|
656
|
381 |
|
$ref = $refResolver->resolveReference($refString); |
657
|
381 |
|
$data = self::unboolSchemaData($ref->getData()); |
658
|
|
|
//$data = $ref->getData(); |
|
|
|
|
659
|
381 |
|
if (!$options->validateOnly) { |
660
|
381 |
|
if ($ref->isImported()) { |
661
|
172 |
|
$refResult = $ref->getImported(); |
662
|
172 |
|
return $refResult; |
663
|
|
|
} |
664
|
381 |
|
if ($result instanceof ObjectItemContract) { |
665
|
381 |
|
$result->setFromRef($refString); |
666
|
|
|
} |
667
|
381 |
|
$ref->setImported($result); |
668
|
381 |
|
$refResult = $this->process($data, $options, $path . '->ref:' . $refString, $result); |
|
|
|
|
669
|
381 |
|
$ref->setImported($refResult); |
670
|
381 |
|
return $refResult; |
671
|
|
|
} else { |
672
|
|
|
$this->process($data, $options, $path . '->ref:' . $refString); |
673
|
|
|
} |
674
|
|
|
} |
675
|
|
|
} catch (InvalidValue $exception) { |
676
|
|
|
$this->fail($exception, $path); |
677
|
|
|
} |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
/** @var Schema[]|null $properties */ |
681
|
3131 |
|
$properties = null; |
682
|
|
|
|
683
|
3131 |
|
$nestedProperties = null; |
684
|
3131 |
|
if ($this->properties !== null) { |
685
|
3117 |
|
$properties = $this->properties->toArray(); // todo call directly |
686
|
3117 |
|
if ($this->properties instanceof Properties) { |
687
|
3117 |
|
$nestedProperties = $this->properties->nestedProperties; |
688
|
|
|
} else { |
689
|
571 |
|
$nestedProperties = array(); |
690
|
|
|
} |
691
|
|
|
} |
692
|
|
|
|
693
|
3131 |
|
$array = array(); |
694
|
3131 |
|
if (!empty($this->__dataToProperty[$options->mapping])) { // todo skip on $options->validateOnly |
695
|
3101 |
|
foreach ((array)$data as $key => $value) { |
696
|
3097 |
|
if ($import) { |
697
|
3096 |
|
if (isset($this->__dataToProperty[$options->mapping][$key])) { |
698
|
3096 |
|
$key = $this->__dataToProperty[$options->mapping][$key]; |
699
|
|
|
} |
700
|
|
|
} else { |
701
|
20 |
|
if (isset($this->__propertyToData[$options->mapping][$key])) { |
702
|
1 |
|
$key = $this->__propertyToData[$options->mapping][$key]; |
703
|
|
|
} |
704
|
|
|
} |
705
|
3101 |
|
$array[$key] = $value; |
706
|
|
|
} |
707
|
|
|
} else { |
708
|
1224 |
|
$array = (array)$data; |
709
|
|
|
} |
710
|
|
|
|
711
|
3131 |
|
if (!$options->skipValidation) { |
712
|
3110 |
|
if ($this->minProperties !== null && count($array) < $this->minProperties) { |
713
|
4 |
|
$this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path); |
714
|
|
|
} |
715
|
3110 |
|
if ($this->maxProperties !== null && count($array) > $this->maxProperties) { |
716
|
3 |
|
$this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path); |
717
|
|
|
} |
718
|
3110 |
|
if ($this->propertyNames !== null) { |
719
|
17 |
|
$propertyNames = self::unboolSchema($this->propertyNames); |
720
|
17 |
|
foreach ($array as $key => $tmp) { |
721
|
10 |
|
$propertyNames->process($key, $options, $path . '->propertyNames:' . $key); |
722
|
|
|
} |
723
|
|
|
} |
724
|
|
|
} |
725
|
|
|
|
726
|
3131 |
|
$defaultApplied = array(); |
727
|
3131 |
|
if ($import |
728
|
3131 |
|
&& !$options->validateOnly |
729
|
3131 |
|
&& $options->applyDefaults |
730
|
3131 |
|
&& $properties !== null |
731
|
3131 |
|
&& $this->objectItemClass !== 'Swaggest\JsonSchema\Schema' // todo replace literal |
732
|
|
|
) { |
733
|
350 |
|
foreach ($properties as $key => $property) { |
734
|
348 |
|
if (isset($property->default)) { |
735
|
17 |
|
if (isset($this->__dataToProperty[$options->mapping][$key])) { |
736
|
|
|
$key = $this->__dataToProperty[$options->mapping][$key]; |
737
|
|
|
} |
738
|
17 |
|
if (!array_key_exists($key, $array)) { |
739
|
11 |
|
$defaultApplied[$key] = true; |
740
|
348 |
|
$array[$key] = $property->default; |
741
|
|
|
} |
742
|
|
|
} |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
|
746
|
3131 |
|
foreach ($array as $key => $value) { |
747
|
3121 |
|
if ($key === '' && PHP_VERSION_ID < 71000) { |
748
|
1 |
|
$this->fail(new InvalidValue('Empty property name'), $path); |
749
|
|
|
} |
750
|
|
|
|
751
|
3120 |
|
$found = false; |
752
|
|
|
|
753
|
3120 |
|
if (!$options->skipValidation && !empty($this->dependencies)) { |
754
|
73 |
|
$deps = $this->dependencies; |
755
|
73 |
|
if (isset($deps->$key)) { |
756
|
63 |
|
$dependencies = $deps->$key; |
757
|
63 |
|
$dependencies = self::unboolSchema($dependencies); |
758
|
63 |
|
if ($dependencies instanceof Schema) { |
759
|
29 |
|
$dependencies->process($data, $options, $path . '->dependencies:' . $key); |
760
|
|
|
} else { |
761
|
34 |
|
foreach ($dependencies as $item) { |
762
|
31 |
|
if (!property_exists($data, $item)) { |
763
|
18 |
|
$this->fail(new ObjectException('Dependency property missing: ' . $item, |
764
|
31 |
|
ObjectException::DEPENDENCY_MISSING), $path); |
765
|
|
|
} |
766
|
|
|
} |
767
|
|
|
} |
768
|
|
|
} |
769
|
|
|
} |
770
|
|
|
|
771
|
3120 |
|
$propertyFound = false; |
772
|
3120 |
|
if (isset($properties[$key])) { |
773
|
|
|
/** @var Schema[] $properties */ |
774
|
3109 |
|
$prop = self::unboolSchema($properties[$key]); |
775
|
3109 |
|
$propertyFound = true; |
776
|
3109 |
|
$found = true; |
777
|
3109 |
|
if ($prop instanceof Schema) { |
778
|
3052 |
|
$value = $prop->process( |
779
|
3052 |
|
$value, |
780
|
3052 |
|
isset($defaultApplied[$key]) ? $options->withSkipValidation() : $options, |
781
|
3052 |
|
$path . '->properties:' . $key |
782
|
|
|
); |
783
|
|
|
} |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
/** @var Egg[] $nestedEggs */ |
787
|
3115 |
|
$nestedEggs = null; |
788
|
3115 |
|
if (isset($nestedProperties[$key])) { |
789
|
6 |
|
$found = true; |
790
|
6 |
|
$nestedEggs = $nestedProperties[$key]; |
791
|
|
|
// todo iterate all nested props? |
792
|
6 |
|
$value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key); |
793
|
|
|
} |
794
|
|
|
|
795
|
3115 |
|
if ($this->patternProperties !== null) { |
796
|
164 |
|
foreach ($this->patternProperties as $pattern => $propertySchema) { |
797
|
164 |
|
if (preg_match(Helper::toPregPattern($pattern), $key)) { |
798
|
118 |
|
$found = true; |
799
|
118 |
|
$value = self::unboolSchema($propertySchema)->process($value, $options, |
800
|
118 |
|
$path . '->patternProperties[' . $pattern . ']:' . $key); |
801
|
92 |
|
if (!$options->validateOnly && $import) { |
802
|
143 |
|
$result->addPatternPropertyName($pattern, $key); |
803
|
|
|
} |
804
|
|
|
//break; // todo manage multiple import data properly (pattern accessor) |
805
|
|
|
} |
806
|
|
|
} |
807
|
|
|
} |
808
|
3115 |
|
if (!$found && $this->additionalProperties !== null) { |
809
|
986 |
|
if (!$options->skipValidation && $this->additionalProperties === false) { |
810
|
11 |
|
$this->fail(new ObjectException('Additional properties not allowed'), $path . ':' . $key); |
811
|
|
|
} |
812
|
|
|
|
813
|
986 |
|
if ($this->additionalProperties instanceof Schema) { |
814
|
986 |
|
$value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key); |
815
|
|
|
} |
816
|
|
|
|
817
|
979 |
|
if ($import && !$this->useObjectAsArray && !$options->validateOnly) { |
818
|
978 |
|
$result->addAdditionalPropertyName($key); |
819
|
|
|
} |
820
|
|
|
} |
821
|
|
|
|
822
|
3111 |
|
if (!$options->validateOnly && $nestedEggs && $import) { |
823
|
5 |
|
foreach ($nestedEggs as $nestedEgg) { |
824
|
5 |
|
$result->setNestedProperty($key, $value, $nestedEgg); |
825
|
|
|
} |
826
|
5 |
|
if ($propertyFound) { |
827
|
5 |
|
$result->$key = $value; |
828
|
|
|
} |
829
|
|
|
} else { |
830
|
3110 |
|
if ($this->useObjectAsArray && $import) { |
831
|
1 |
|
$result[$key] = $value; |
832
|
|
|
} else { |
833
|
3110 |
|
if ($found || !$import) { |
834
|
3108 |
|
$result->$key = $value; |
835
|
1134 |
|
} elseif (!isset($result->$key)) { |
836
|
3111 |
|
$result->$key = $value; |
837
|
|
|
} |
838
|
|
|
} |
839
|
|
|
} |
840
|
|
|
} |
841
|
|
|
|
842
|
3119 |
|
return $result; |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* @param array $data |
847
|
|
|
* @param Context $options |
848
|
|
|
* @param string $path |
849
|
|
|
* @param array $result |
850
|
|
|
* @return mixed |
851
|
|
|
* @throws InvalidValue |
852
|
|
|
* @throws \Exception |
853
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
854
|
|
|
*/ |
855
|
1200 |
|
private function processArray($data, Context $options, $path, $result) |
856
|
|
|
{ |
857
|
1200 |
|
$count = count($data); |
858
|
1200 |
|
if (!$options->skipValidation) { |
859
|
1000 |
|
if ($this->minItems !== null && $count < $this->minItems) { |
860
|
9 |
|
$this->fail(new ArrayException("Not enough items in array"), $path); |
861
|
|
|
} |
862
|
|
|
|
863
|
994 |
|
if ($this->maxItems !== null && $count > $this->maxItems) { |
864
|
6 |
|
$this->fail(new ArrayException("Too many items in array"), $path); |
865
|
|
|
} |
866
|
|
|
} |
867
|
|
|
|
868
|
1188 |
|
$pathItems = 'items'; |
869
|
1188 |
|
$this->items = self::unboolSchema($this->items); |
870
|
1188 |
|
if ($this->items instanceof Schema) { |
871
|
842 |
|
$items = array(); |
872
|
842 |
|
$additionalItems = $this->items; |
873
|
681 |
|
} elseif ($this->items === null) { // items defaults to empty schema so everything is valid |
874
|
681 |
|
$items = array(); |
875
|
681 |
|
$additionalItems = true; |
876
|
|
|
} else { // listed items |
877
|
104 |
|
$items = $this->items; |
878
|
104 |
|
$additionalItems = $this->additionalItems; |
879
|
104 |
|
$pathItems = 'additionalItems'; |
880
|
|
|
} |
881
|
|
|
|
882
|
|
|
/** |
883
|
|
|
* @var Schema|Schema[] $items |
884
|
|
|
* @var null|bool|Schema $additionalItems |
885
|
|
|
*/ |
886
|
1188 |
|
$itemsLen = is_array($items) ? count($items) : 0; |
887
|
1188 |
|
$index = 0; |
888
|
1188 |
|
foreach ($result as $key => $value) { |
889
|
1063 |
|
if ($index < $itemsLen) { |
890
|
94 |
|
$itemSchema = self::unboolSchema($items[$index]); |
891
|
94 |
|
$result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index); |
892
|
|
|
} else { |
893
|
1063 |
|
if ($additionalItems instanceof Schema) { |
894
|
816 |
|
$result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems |
895
|
816 |
|
. '[' . $index . ']'); |
896
|
565 |
|
} elseif (!$options->skipValidation && $additionalItems === false) { |
897
|
6 |
|
$this->fail(new ArrayException('Unexpected array item'), $path); |
898
|
|
|
} |
899
|
|
|
} |
900
|
1043 |
|
++$index; |
901
|
|
|
} |
902
|
|
|
|
903
|
1164 |
|
if (!$options->skipValidation && $this->uniqueItems) { |
904
|
498 |
|
if (!UniqueItems::isValid($data)) { |
905
|
19 |
|
$this->fail(new ArrayException('Array is not unique'), $path); |
906
|
|
|
} |
907
|
|
|
} |
908
|
|
|
|
909
|
1145 |
|
if (!$options->skipValidation && $this->contains !== null) { |
910
|
|
|
/** @var Schema|bool $contains */ |
911
|
36 |
|
$contains = $this->contains; |
912
|
36 |
|
if ($contains === false) { |
913
|
4 |
|
$this->fail(new ArrayException('Contains is false'), $path); |
914
|
|
|
} |
915
|
32 |
|
if ($count === 0) { |
916
|
7 |
|
$this->fail(new ArrayException('Empty array fails contains constraint'), $path); |
917
|
|
|
} |
918
|
25 |
|
if ($contains === true) { |
919
|
2 |
|
$contains = self::unboolSchema($contains); |
920
|
|
|
} |
921
|
25 |
|
$containsOk = false; |
922
|
25 |
|
foreach ($data as $key => $item) { |
923
|
|
|
try { |
924
|
25 |
|
$contains->process($item, $options, $path . '->' . $key); |
925
|
17 |
|
$containsOk = true; |
926
|
17 |
|
break; |
927
|
21 |
|
} catch (InvalidValue $exception) { |
|
|
|
|
928
|
|
|
} |
929
|
|
|
} |
930
|
25 |
|
if (!$containsOk) { |
931
|
8 |
|
$this->fail(new ArrayException('Array fails contains constraint'), $path); |
932
|
|
|
} |
933
|
|
|
} |
934
|
1126 |
|
return $result; |
935
|
|
|
} |
936
|
|
|
|
937
|
|
|
/** |
938
|
|
|
* @param mixed|string $data |
939
|
|
|
* @param Context $options |
940
|
|
|
* @param string $path |
941
|
|
|
* @return bool|mixed|string |
942
|
|
|
* @throws InvalidValue |
943
|
|
|
*/ |
944
|
14 |
|
private function processContent($data, Context $options, $path) |
945
|
|
|
{ |
946
|
|
|
try { |
947
|
14 |
|
if ($options->unpackContentMediaType) { |
948
|
7 |
|
return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import); |
949
|
|
|
} else { |
950
|
7 |
|
Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true); |
951
|
|
|
} |
952
|
4 |
|
} catch (InvalidValue $exception) { |
953
|
4 |
|
$this->fail($exception, $path); |
954
|
|
|
} |
955
|
7 |
|
return $data; |
956
|
|
|
} |
957
|
|
|
|
958
|
|
|
/** |
959
|
|
|
* @param mixed $data |
960
|
|
|
* @param Context $options |
961
|
|
|
* @param string $path |
962
|
|
|
* @param null $result |
963
|
|
|
* @return array|mixed|null|object|\stdClass |
964
|
|
|
* @throws InvalidValue |
965
|
|
|
* @throws \Exception |
966
|
|
|
* @throws \Swaggest\JsonDiff\Exception |
967
|
|
|
*/ |
968
|
3178 |
|
public function process($data, Context $options, $path = '#', $result = null) |
969
|
|
|
{ |
970
|
|
|
|
971
|
3178 |
|
$import = $options->import; |
972
|
|
|
//$pathTrace = explode('->', $path); |
|
|
|
|
973
|
|
|
|
974
|
3178 |
|
if (!$import && $data instanceof ObjectItemContract) { |
975
|
779 |
|
$result = new \stdClass(); |
976
|
779 |
|
if ($options->circularReferences->contains($data)) { |
977
|
|
|
/** @noinspection PhpIllegalArrayKeyTypeInspection */ |
978
|
1 |
|
$path = $options->circularReferences[$data]; |
979
|
|
|
// @todo $path is not a valid json pointer $ref |
980
|
1 |
|
$result->{self::REF} = $path; |
981
|
1 |
|
return $result; |
982
|
|
|
// return $options->circularReferences[$data]; |
|
|
|
|
983
|
|
|
} |
984
|
779 |
|
$options->circularReferences->attach($data, $path); |
985
|
|
|
//$options->circularReferences->attach($data, $result); |
|
|
|
|
986
|
|
|
|
987
|
779 |
|
$data = $data->jsonSerialize(); |
988
|
|
|
} |
989
|
3178 |
|
if (!$import && is_array($data) && $this->useObjectAsArray) { |
990
|
1 |
|
$data = (object)$data; |
991
|
|
|
} |
992
|
|
|
|
993
|
3178 |
|
if (null !== $options->dataPreProcessor) { |
994
|
|
|
$data = $options->dataPreProcessor->process($data, $this, $import); |
995
|
|
|
} |
996
|
|
|
|
997
|
3178 |
|
if ($result === null) { |
998
|
3178 |
|
$result = $data; |
999
|
|
|
} |
1000
|
|
|
|
1001
|
3178 |
|
if ($options->skipValidation) { |
1002
|
1422 |
|
goto skipValidation; |
1003
|
|
|
} |
1004
|
|
|
|
1005
|
3140 |
|
if ($this->type !== null) { |
1006
|
3119 |
|
$this->processType($data, $options, $path); |
1007
|
|
|
} |
1008
|
|
|
|
1009
|
3139 |
|
if ($this->enum !== null) { |
1010
|
1457 |
|
$this->processEnum($data, $path); |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
3139 |
|
if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) { |
1014
|
43 |
|
$this->processConst($data, $path); |
1015
|
|
|
} |
1016
|
|
|
|
1017
|
3139 |
|
if ($this->not !== null) { |
1018
|
68 |
|
$this->processNot($data, $options, $path); |
1019
|
|
|
} |
1020
|
|
|
|
1021
|
3139 |
|
if (is_string($data)) { |
1022
|
2227 |
|
$this->processString($data, $path); |
1023
|
|
|
} |
1024
|
|
|
|
1025
|
3139 |
|
if (is_int($data) || is_float($data)) { |
1026
|
1037 |
|
$this->processNumeric($data, $path); |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
3139 |
|
if ($this->if !== null) { |
1030
|
26 |
|
$result = $this->processIf($data, $options, $path); |
1031
|
|
|
} |
1032
|
|
|
|
1033
|
|
|
skipValidation: |
1034
|
|
|
|
1035
|
3177 |
|
if ($this->oneOf !== null) { |
1036
|
99 |
|
$result = $this->processOneOf($data, $options, $path); |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
3177 |
|
if ($this->anyOf !== null) { |
1040
|
1703 |
|
$result = $this->processAnyOf($data, $options, $path); |
1041
|
|
|
} |
1042
|
|
|
|
1043
|
3177 |
|
if ($this->allOf !== null) { |
1044
|
351 |
|
$result = $this->processAllOf($data, $options, $path); |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
3177 |
|
if ($data instanceof \stdClass) { |
1048
|
3132 |
|
$result = $this->processObject($data, $options, $path, $result); |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
3174 |
|
if (is_array($data)) { |
1052
|
1200 |
|
$result = $this->processArray($data, $options, $path, $result); |
1053
|
|
|
} |
1054
|
|
|
|
1055
|
3174 |
|
if ($this->contentEncoding !== null || $this->contentMediaType !== null) { |
1056
|
14 |
|
$result = $this->processContent($data, $options, $path); |
1057
|
|
|
} |
1058
|
|
|
|
1059
|
3174 |
|
return $result; |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
|
|
/** |
1063
|
|
|
* @param boolean $useObjectAsArray |
1064
|
|
|
* @return Schema |
1065
|
|
|
*/ |
1066
|
1 |
|
public function setUseObjectAsArray($useObjectAsArray) |
1067
|
|
|
{ |
1068
|
1 |
|
$this->useObjectAsArray = $useObjectAsArray; |
1069
|
1 |
|
return $this; |
1070
|
|
|
} |
1071
|
|
|
|
1072
|
|
|
/** |
1073
|
|
|
* @param InvalidValue $exception |
1074
|
|
|
* @param string $path |
1075
|
|
|
* @throws InvalidValue |
1076
|
|
|
*/ |
1077
|
1211 |
|
private function fail(InvalidValue $exception, $path) |
1078
|
|
|
{ |
1079
|
1211 |
|
if ($path !== '#') { |
1080
|
729 |
|
$exception->addPath($path); |
1081
|
|
|
} |
1082
|
1211 |
|
throw $exception; |
1083
|
|
|
} |
1084
|
|
|
|
1085
|
14 |
|
public static function integer() |
1086
|
|
|
{ |
1087
|
14 |
|
$schema = new static(); |
1088
|
14 |
|
$schema->type = Type::INTEGER; |
1089
|
14 |
|
return $schema; |
1090
|
|
|
} |
1091
|
|
|
|
1092
|
5 |
|
public static function number() |
1093
|
|
|
{ |
1094
|
5 |
|
$schema = new static(); |
1095
|
5 |
|
$schema->type = Type::NUMBER; |
1096
|
5 |
|
return $schema; |
1097
|
|
|
} |
1098
|
|
|
|
1099
|
9 |
|
public static function string() |
1100
|
|
|
{ |
1101
|
9 |
|
$schema = new static(); |
1102
|
9 |
|
$schema->type = Type::STRING; |
1103
|
9 |
|
return $schema; |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
5 |
|
public static function boolean() |
1107
|
|
|
{ |
1108
|
5 |
|
$schema = new static(); |
1109
|
5 |
|
$schema->type = Type::BOOLEAN; |
1110
|
5 |
|
return $schema; |
1111
|
|
|
} |
1112
|
|
|
|
1113
|
7 |
|
public static function object() |
1114
|
|
|
{ |
1115
|
7 |
|
$schema = new static(); |
1116
|
7 |
|
$schema->type = Type::OBJECT; |
1117
|
7 |
|
return $schema; |
1118
|
|
|
} |
1119
|
|
|
|
1120
|
2 |
|
public static function arr() |
1121
|
|
|
{ |
1122
|
2 |
|
$schema = new static(); |
1123
|
2 |
|
$schema->type = Type::ARR; |
1124
|
2 |
|
return $schema; |
1125
|
|
|
} |
1126
|
|
|
|
1127
|
|
|
public static function null() |
1128
|
|
|
{ |
1129
|
|
|
$schema = new static(); |
1130
|
|
|
$schema->type = Type::NULL; |
1131
|
|
|
return $schema; |
1132
|
|
|
} |
1133
|
|
|
|
1134
|
|
|
|
1135
|
|
|
/** |
1136
|
|
|
* @param Properties $properties |
1137
|
|
|
* @return Schema |
1138
|
|
|
*/ |
1139
|
3 |
|
public function setProperties($properties) |
1140
|
|
|
{ |
1141
|
3 |
|
$this->properties = $properties; |
1142
|
3 |
|
return $this; |
1143
|
|
|
} |
1144
|
|
|
|
1145
|
|
|
/** |
1146
|
|
|
* @param string $name |
1147
|
|
|
* @param Schema $schema |
1148
|
|
|
* @return $this |
1149
|
|
|
*/ |
1150
|
3 |
|
public function setProperty($name, $schema) |
1151
|
|
|
{ |
1152
|
3 |
|
if (null === $this->properties) { |
1153
|
3 |
|
$this->properties = new Properties(); |
1154
|
|
|
} |
1155
|
3 |
|
$this->properties->__set($name, $schema); |
1156
|
3 |
|
return $this; |
1157
|
|
|
} |
1158
|
|
|
|
1159
|
|
|
/** @var Meta[] */ |
1160
|
|
|
private $metaItems = array(); |
1161
|
|
|
|
1162
|
1 |
|
public function addMeta(Meta $meta) |
1163
|
|
|
{ |
1164
|
1 |
|
$this->metaItems[get_class($meta)] = $meta; |
1165
|
1 |
|
return $this; |
1166
|
|
|
} |
1167
|
|
|
|
1168
|
1 |
|
public function getMeta($className) |
1169
|
|
|
{ |
1170
|
1 |
|
if (isset($this->metaItems[$className])) { |
1171
|
1 |
|
return $this->metaItems[$className]; |
1172
|
|
|
} |
1173
|
|
|
return null; |
1174
|
|
|
} |
1175
|
|
|
|
1176
|
|
|
/** |
1177
|
|
|
* @param Context $options |
1178
|
|
|
* @return ObjectItemContract |
1179
|
|
|
*/ |
1180
|
5 |
|
public function makeObjectItem(Context $options = null) |
1181
|
|
|
{ |
1182
|
5 |
|
if (null === $this->objectItemClass) { |
1183
|
|
|
return new ObjectItem(); |
1184
|
|
|
} else { |
1185
|
5 |
|
$className = $this->objectItemClass; |
1186
|
5 |
|
if ($options !== null) { |
1187
|
|
|
if (isset($options->objectItemClassMapping[$className])) { |
1188
|
|
|
$className = $options->objectItemClassMapping[$className]; |
1189
|
|
|
} |
1190
|
|
|
} |
1191
|
5 |
|
return new $className; |
1192
|
|
|
} |
1193
|
|
|
} |
1194
|
|
|
|
1195
|
|
|
/** |
1196
|
|
|
* @param mixed $schema |
1197
|
|
|
* @return mixed|Schema |
1198
|
|
|
*/ |
1199
|
3190 |
|
private static function unboolSchema($schema) |
1200
|
|
|
{ |
1201
|
3190 |
|
static $trueSchema; |
1202
|
3190 |
|
static $falseSchema; |
1203
|
|
|
|
1204
|
3190 |
|
if (null === $trueSchema) { |
1205
|
1 |
|
$trueSchema = new Schema(); |
1206
|
1 |
|
$trueSchema->__booleanSchema = true; |
1207
|
1 |
|
$falseSchema = new Schema(); |
1208
|
1 |
|
$falseSchema->not = $trueSchema; |
1209
|
1 |
|
$falseSchema->__booleanSchema = false; |
1210
|
|
|
} |
1211
|
|
|
|
1212
|
3190 |
|
if ($schema === true) { |
1213
|
108 |
|
return $trueSchema; |
1214
|
3162 |
|
} elseif ($schema === false) { |
1215
|
94 |
|
return $falseSchema; |
1216
|
|
|
} else { |
1217
|
3130 |
|
return $schema; |
1218
|
|
|
} |
1219
|
|
|
} |
1220
|
|
|
|
1221
|
|
|
/** |
1222
|
|
|
* @param mixed $data |
1223
|
|
|
* @return \stdClass |
1224
|
|
|
*/ |
1225
|
381 |
|
private static function unboolSchemaData($data) |
1226
|
|
|
{ |
1227
|
381 |
|
static $trueSchema; |
1228
|
381 |
|
static $falseSchema; |
1229
|
|
|
|
1230
|
381 |
|
if (null === $trueSchema) { |
1231
|
1 |
|
$trueSchema = new \stdClass(); |
1232
|
1 |
|
$falseSchema = new \stdClass(); |
1233
|
1 |
|
$falseSchema->not = $trueSchema; |
1234
|
|
|
} |
1235
|
|
|
|
1236
|
381 |
|
if ($data === true) { |
1237
|
6 |
|
return $trueSchema; |
1238
|
377 |
|
} elseif ($data === false) { |
1239
|
5 |
|
return $falseSchema; |
1240
|
|
|
} else { |
1241
|
372 |
|
return $data; |
1242
|
|
|
} |
1243
|
|
|
} |
1244
|
|
|
|
1245
|
|
|
} |
1246
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.