Skip to content

Commit cd3d176

Browse files
committed
spec compliance
Tests: 275, Assertions: 259, Errors: 16, Failures: 32.
1 parent 0600dcf commit cd3d176

File tree

5 files changed

+193
-31
lines changed

5 files changed

+193
-31
lines changed

src/Constraint/Properties.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ public static function create()
3737
return new static;
3838
}
3939

40+
/**
41+
* @param $data
42+
* @param ObjectItem $result
43+
* @param Schema|null $schema
44+
* @throws Exception
45+
* @throws \Exception
46+
* @deprecated move relevant code to Schema
47+
*/
4048
public function import($data, ObjectItem $result, Schema $schema = null)
4149
{
4250
$traceHelper = Schema::$traceHelper;
@@ -49,7 +57,19 @@ public function import($data, ObjectItem $result, Schema $schema = null)
4957
$result->$key = $this->_arrayOfData[$key]->import($value);
5058
$traceHelper->pop();
5159
} else {
52-
if (null !== $additionalProperties) {
60+
// todo remove code duplication
61+
62+
$found = false;
63+
if ($schema->patternProperties !== null) {
64+
foreach ($schema->patternProperties as $pattern => $propertySchema) {
65+
if (preg_match($pattern, $key)) {
66+
$found = true;
67+
$value = $propertySchema->import($value);
68+
//break; // todo manage multiple import data properly (pattern accessor)
69+
}
70+
}
71+
}
72+
if (!$found && null !== $additionalProperties) {
5373
$traceHelper->push()->addData(SchemaLoader::ADDITIONAL_PROPERTIES);
5474
if ($additionalProperties === false) {
5575
throw new Exception('Additional properties not allowed', Exception::INVALID_VALUE);

src/Helper.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Yaoi\Schema;
4+
5+
6+
class Helper
7+
{
8+
public static function toPregPattern($jsonPattern)
9+
{
10+
static $delimiters = array('/', '#', '+', '~', '%');
11+
12+
$pattern = false;
13+
foreach ($delimiters as $delimiter) {
14+
if (strpos($jsonPattern, $delimiter) === false) {
15+
$pattern = $delimiter . $jsonPattern . $delimiter . 'u';
16+
break;
17+
}
18+
}
19+
20+
if (false === $pattern) {
21+
throw new Exception('Failed to prepare preg pattern');
22+
}
23+
24+
if (@preg_match($pattern, '') === false) {
25+
throw new Exception('Regex pattern is invalid: ' . $jsonPattern);
26+
}
27+
28+
return $pattern;
29+
}
30+
31+
}

src/Schema.php

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Yaoi\Schema;
44

55

6-
use Yaoi\Schema\Constraint\InvalidValue;
76
use Yaoi\Schema\Constraint\Properties;
87
use Yaoi\Schema\Constraint\Ref;
98
use Yaoi\Schema\Constraint\Type;
@@ -18,29 +17,44 @@ class Schema extends MagicMap
1817
/** @var Type */
1918
public $type;
2019

20+
// Object
2121
/** @var Properties */
2222
public $properties;
2323
/** @var Schema|bool */
2424
public $additionalProperties;
25-
26-
25+
/** @var Schema[] */
26+
public $patternProperties;
27+
/** @var string[] */
28+
public $required;
29+
/** @var string[][]|Schema[] */
30+
public $dependencies;
31+
32+
// Array
2733
/** @var Schema|Schema[] */
2834
public $items;
2935
/** @var Schema|bool */
3036
public $additionalItems;
3137
/** @var bool */
3238
public $uniqueItems;
3339

40+
// Reference
3441
/** @var Ref */
3542
public $ref;
3643

44+
// Enum
45+
/** @var array */
46+
public $enum;
3747

48+
// Number
3849
public $maximum;
3950
public $exclusiveMaximum;
4051
public $minimum;
4152
public $exclusiveMinimum;
4253

4354

55+
// String
56+
public $pattern;
57+
4458
public function import($data)
4559
{
4660
$result = $data;
@@ -54,6 +68,27 @@ public function import($data)
5468
}
5569
}
5670

71+
if ($this->enum !== null) {
72+
$enumOk = false;
73+
foreach ($this->enum as $item) {
74+
if ($item === $data) {
75+
$enumOk = true;
76+
break;
77+
}
78+
}
79+
if (!$enumOk) {
80+
$this->fail('Enum failed');
81+
}
82+
}
83+
84+
if (is_string($data)) {
85+
if ($this->pattern) {
86+
if (0 === preg_match($this->pattern, $data)) {
87+
$this->fail('Does not match to ' . $this->pattern);
88+
}
89+
}
90+
}
91+
5792
if (is_int($data) || is_float($data)) {
5893
if ($this->maximum !== null) {
5994
if ($this->exclusiveMaximum === true) {
@@ -83,35 +118,62 @@ public function import($data)
83118
}
84119

85120
if ($data instanceof \stdClass) {
86-
if ($this->properties !== null) {
87-
if (!$result instanceof ObjectItem) {
88-
$result = new ObjectItem();
89-
}
90-
try {
91-
$this->properties->import($data, $result, $this);
92-
} catch (Exception $exception) {
93-
if ($exception->getCode() === Exception::INVALID_VALUE) {
94-
$this->fail($exception->getMessage());
121+
if ($this->required !== null) {
122+
foreach ($this->required as $item) {
123+
if (!property_exists($data, $item)) {
124+
$this->fail('Required property missing: ' . $item);
95125
}
96126
}
127+
}
97128

98-
} else {
99-
if ($this->additionalProperties
100-
|| ($this->type && $this->type->has(Type::OBJECT))
101-
) {
129+
if (!$result instanceof ObjectItem) {
130+
$result = new ObjectItem();
131+
}
102132

103-
if (!$result instanceof ObjectItem) {
104-
$result = new ObjectItem();
133+
if ($this->properties !== null) {
134+
/** @var Schema[] $properties */
135+
$properties = &$this->properties->toArray();
136+
}
137+
138+
foreach ((array)$data as $key => $value) {
139+
$found = false;
140+
if (isset($this->dependencies[$key])) {
141+
$dependencies = $this->dependencies[$key];
142+
if ($dependencies instanceof Schema) {
143+
$dependencies->import($data);
144+
} else {
145+
foreach ($dependencies as $item) {
146+
if (!property_exists($data, $item)) {
147+
$this->fail('Dependency property missing: ' . $item);
148+
}
149+
}
105150
}
151+
}
152+
153+
if (isset($properties[$key])) {
154+
$found = true;
155+
$value = $properties[$key]->import($value);
156+
}
106157

107-
foreach ((array)$data as $key => $value) {
108-
if ($this->additionalProperties !== null) {
109-
$value = $this->additionalProperties->import($value);
158+
if (!$found && $this->patternProperties !== null) {
159+
foreach ($this->patternProperties as $pattern => $propertySchema) {
160+
if (preg_match($pattern, $key)) {
161+
$found = true;
162+
$value = $propertySchema->import($value);
163+
//break; // todo manage multiple import data properly (pattern accessor)
110164
}
111-
$result[$key] = $value;
112165
}
113166
}
167+
if (!$found && $this->additionalProperties !== null) {
168+
if ($this->additionalProperties === false) {
169+
$this->fail('Additional properties not allowed');
170+
}
171+
172+
$value = $this->additionalProperties->import($value);
173+
}
174+
$result[$key] = $value;
114175
}
176+
115177
}
116178

117179
if (is_array($data)) {

src/SchemaLoader.php

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,35 @@
22

33
namespace Yaoi\Schema;
44

5-
use Yaoi\Schema\Base;
65
use Yaoi\Schema\Constraint\Properties;
76
use Yaoi\Schema\Constraint\Ref;
87
use Yaoi\Schema\Constraint\Type;
9-
use Yaoi\Schema\Schema;
108

119
class SchemaLoader extends Base
1210
{
1311
const TYPE = 'type';
1412

1513
const PROPERTIES = 'properties';
14+
const PATTERN_PROPERTIES = 'patternProperties';
1615
const ADDITIONAL_PROPERTIES = 'additionalProperties';
16+
const REQUIRED = 'required';
17+
const DEPENDENCIES = 'dependencies';
18+
1719
const REF = '$ref';
1820

1921
const ITEMS = 'items';
2022
const ADDITIONAL_ITEMS = 'additionalItems';
2123
const UNIQUE_ITEMS = 'uniqueItems';
2224

25+
const ENUM = 'enum';
26+
2327
const MINIMUM = 'minimum';
2428
const EXCLUSIVE_MINIMUM = 'exclusiveMinimum';
2529
const MAXIMUM = 'maximum';
2630
const EXCLUSIVE_MAXIMUM = 'exclusiveMaximum';
2731

32+
const PATTERN = 'pattern';
33+
2834
/** @var Schema */
2935
private $rootSchema;
3036

@@ -40,6 +46,7 @@ public function readSchema($schemaData)
4046
}
4147

4248

49+
4350
protected function readSchemaDeeper($schemaArray, Schema $parentSchema = null)
4451
{
4552
$schema = new Schema();
@@ -56,24 +63,48 @@ protected function readSchemaDeeper($schemaArray, Schema $parentSchema = null)
5663
$schema->type = new Type($schemaArray[self::TYPE]);
5764
}
5865

66+
67+
// Object
5968
if (isset($schemaArray[self::PROPERTIES])) {
6069
$properties = new Properties();
6170
$schema->properties = $properties;
6271
foreach ($schemaArray[self::PROPERTIES] as $name => $data) {
63-
$properties->__set($name, $this->readSchemaDeeper($data, $schema));
72+
$properties->__set($name, $this->readSchemaDeeper($data));
73+
}
74+
}
75+
76+
if (isset($schemaArray[self::PATTERN_PROPERTIES])) {
77+
foreach ($schemaArray[self::PATTERN_PROPERTIES] as $name => $data) {
78+
$schema->patternProperties[Helper::toPregPattern($name)] = $this->readSchemaDeeper($data);
6479
}
6580
}
6681

6782
if (isset($schemaArray[self::ADDITIONAL_PROPERTIES])) {
6883
$additionalProperties = $schemaArray[self::ADDITIONAL_PROPERTIES];
6984
if ($additionalProperties instanceof \stdClass) {
70-
$schema->additionalProperties = $this->readSchemaDeeper($additionalProperties, $schema);
85+
$schema->additionalProperties = $this->readSchemaDeeper($additionalProperties);
7186
} else {
7287
$schema->additionalProperties = $additionalProperties;
7388
}
7489
}
7590

91+
if (isset($schemaArray[self::REQUIRED])) {
92+
$schema->required = $schemaArray[self::REQUIRED];
93+
}
94+
95+
if (isset($schemaArray[self::DEPENDENCIES])) {
96+
foreach ($schemaArray[self::DEPENDENCIES] as $key => $value) {
97+
if ($value instanceof \stdClass) {
98+
$schema->dependencies[$key] = $this->readSchemaDeeper($value);
99+
} else {
100+
$schema->dependencies[$key] = $value;
101+
}
102+
}
103+
}
104+
76105

106+
107+
// Array
77108
if (isset($schemaArray[self::ITEMS])) {
78109
$items = $schemaArray[self::ITEMS];
79110
if (is_array($items)) {
@@ -90,7 +121,7 @@ protected function readSchemaDeeper($schemaArray, Schema $parentSchema = null)
90121
if (isset($schemaArray[self::ADDITIONAL_ITEMS])) {
91122
$additionalItems = $schemaArray[self::ADDITIONAL_ITEMS];
92123
if ($additionalItems instanceof \stdClass) {
93-
$schema->additionalItems = $this->readSchemaDeeper($additionalItems, $schema);
124+
$schema->additionalItems = $this->readSchemaDeeper($additionalItems);
94125
} else {
95126
$schema->additionalItems = $additionalItems;
96127
}
@@ -101,6 +132,8 @@ protected function readSchemaDeeper($schemaArray, Schema $parentSchema = null)
101132
}
102133

103134

135+
136+
// Number
104137
if (isset($schemaArray[self::MINIMUM])) {
105138
$schema->minimum = $schemaArray[self::MINIMUM];
106139
}
@@ -115,6 +148,18 @@ protected function readSchemaDeeper($schemaArray, Schema $parentSchema = null)
115148
}
116149

117150

151+
// String
152+
if (isset($schemaArray[self::PATTERN])) {
153+
$schema->pattern = Helper::toPregPattern($schemaArray[self::PATTERN]);
154+
}
155+
156+
157+
158+
// Misc
159+
if (isset($schemaArray[self::ENUM])) {
160+
$schema->enum = $schemaArray[self::ENUM];
161+
}
162+
118163
// should resolve references on load
119164
if (isset($schemaArray[self::REF])) {
120165
$schema->ref = $this->resolveReference($schemaArray[self::REF]);

0 commit comments

Comments
 (0)