Skip to content

Commit 429be23

Browse files
eraydbighappyface
authored andcommitted
Backports for 5.2.1 (#413)
* Split $objectDefinition into $schema and $properties (#411) Object validation attempts to use a single variable to store both the object definition, and its properties. This causes validation to be incomplete where "properties" is not set, but "additionalProperties" is. This commit fixes both bugs in issue #353. * Issue-414: Allow The Option of T or space for Date time. (#415) * Testcase for minProperties with properties defined (#416) + Fix Test * Tweak phpdocumentor dependency to avoid install conflicts (#421) * [BUGFIX] Cast empty schema arrays to object (#409) * Cast root to object * Use function_exists to allow polyfill compatibility * Move array->object conversion to SchemaConstraint & SchemaStorage Fixes issue #408 * fix bug when applying defaults for array items when the schema is for (#405) all items and add support for minItems when applying defaults * [BUGFIX] Split "uri" format into "uri" & "uri-reference", fix meta-schema bug (#419) * Split "uri" format into "uri" and "uri-reference" * Correct format for id & $ref in draft-03/04 meta-schemas See json-schema-org/JSON-Schema-Test-Suite#177 (comment)
1 parent e3c9bcc commit 429be23

15 files changed

+157
-47
lines changed

Diff for: composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"json-schema/JSON-Schema-Test-Suite": "1.2.0",
4343
"phpunit/phpunit": "^4.8.22",
4444
"friendsofphp/php-cs-fixer": "^2.1",
45-
"phpdocumentor/phpdocumentor": "~2"
45+
"phpdocumentor/phpdocumentor": "^2.7"
4646
},
4747
"autoload": {
4848
"psr-4": { "JsonSchema\\": "src/JsonSchema/" }

Diff for: src/JsonSchema/Constraints/BaseConstraint.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ public static function arrayToObjectRecursive($array)
137137
$json = json_encode($array);
138138
if (json_last_error() !== \JSON_ERROR_NONE) {
139139
$message = 'Unable to encode schema array as JSON';
140-
if (version_compare(phpversion(), '5.5.0', '>=')) {
140+
if (function_exists('json_last_error_msg')) {
141141
$message .= ': ' . json_last_error_msg();
142142
}
143143
throw new InvalidArgumentException($message);
144144
}
145145

146-
return json_decode($json);
146+
return (object) json_decode($json);
147147
}
148148
}

Diff for: src/JsonSchema/Constraints/Constraint.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ protected function checkArray(&$value, $schema = null, JsonPointer $path = null,
7777
* @param mixed $value
7878
* @param mixed $schema
7979
* @param JsonPointer|null $path
80-
* @param mixed $i
80+
* @param mixed $properties
81+
* @param mixed $additionalProperties
8182
* @param mixed $patternProperties
8283
*/
83-
protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $i = null, $patternProperties = null, $appliedDefaults = array())
84+
protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null,
85+
$additionalProperties = null, $patternProperties = null, $appliedDefaults = array())
8486
{
8587
$validator = $this->factory->createInstanceFor('object');
86-
$validator->check($value, $schema, $path, $i, $patternProperties, $appliedDefaults);
88+
$validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);
8789

8890
$this->addErrors($validator->getErrors());
8991
}

Diff for: src/JsonSchema/Constraints/FormatConstraint.php

+7
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i =
8080
break;
8181

8282
case 'uri':
83+
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
84+
$this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
85+
}
86+
break;
87+
88+
case 'uriref':
89+
case 'uri-reference':
8390
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
8491
// FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but
8592
// the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them.

Diff for: src/JsonSchema/Constraints/ObjectConstraint.php

+24-20
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class ObjectConstraint extends Constraint
2727
/**
2828
* {@inheritdoc}
2929
*/
30-
public function check(&$element, $definition = null, JsonPointer $path = null, $additionalProp = null, $patternProperties = null, $appliedDefaults = array())
30+
public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null,
31+
$additionalProp = null, $patternProperties = null, $appliedDefaults = array())
3132
{
3233
if ($element instanceof UndefinedConstraint) {
3334
return;
@@ -37,16 +38,17 @@ public function check(&$element, $definition = null, JsonPointer $path = null, $
3738

3839
$matches = array();
3940
if ($patternProperties) {
41+
// validate the element pattern properties
4042
$matches = $this->validatePatternProperties($element, $path, $patternProperties);
4143
}
4244

43-
if ($definition) {
44-
// validate the definition properties
45-
$this->validateDefinition($element, $definition, $path);
45+
if ($properties) {
46+
// validate the element properties
47+
$this->validateProperties($element, $properties, $path);
4648
}
4749

48-
// additional the element properties
49-
$this->validateElement($element, $matches, $definition, $path, $additionalProp);
50+
// validate additional element properties & constraints
51+
$this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp);
5052
}
5153

5254
public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties)
@@ -81,18 +83,20 @@ public function validatePatternProperties($element, JsonPointer $path = null, $p
8183
/**
8284
* Validates the element properties
8385
*
84-
* @param \stdClass $element Element to validate
85-
* @param array $matches Matches from patternProperties (if any)
86-
* @param \stdClass $objectDefinition ObjectConstraint definition
87-
* @param JsonPointer|null $path Path to test?
88-
* @param mixed $additionalProp Additional properties
86+
* @param \StdClass $element Element to validate
87+
* @param array $matches Matches from patternProperties (if any)
88+
* @param \StdClass $schema ObjectConstraint definition
89+
* @param JsonPointer|null $path Current test path
90+
* @param \StdClass $properties Properties
91+
* @param mixed $additionalProp Additional properties
8992
*/
90-
public function validateElement($element, $matches, $objectDefinition = null, JsonPointer $path = null, $additionalProp = null)
93+
public function validateElement($element, $matches, $schema = null, JsonPointer $path = null,
94+
$properties = null, $additionalProp = null)
9195
{
92-
$this->validateMinMaxConstraint($element, $objectDefinition, $path);
96+
$this->validateMinMaxConstraint($element, $schema, $path);
9397

9498
foreach ($element as $i => $value) {
95-
$definition = $this->getProperty($objectDefinition, $i);
99+
$definition = $this->getProperty($properties, $i);
96100

97101
// no additional properties allowed
98102
if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
@@ -124,17 +128,17 @@ public function validateElement($element, $matches, $objectDefinition = null, Js
124128
/**
125129
* Validates the definition properties
126130
*
127-
* @param \stdClass $element Element to validate
128-
* @param \stdClass $objectDefinition ObjectConstraint definition
129-
* @param JsonPointer|null $path Path?
131+
* @param \stdClass $element Element to validate
132+
* @param \stdClass $properties Property definitions
133+
* @param JsonPointer|null $path Path?
130134
*/
131-
public function validateDefinition(&$element, $objectDefinition = null, JsonPointer $path = null)
135+
public function validateProperties(&$element, $properties = null, JsonPointer $path = null)
132136
{
133137
$undefinedConstraint = $this->factory->createInstanceFor('undefined');
134138

135-
foreach ($objectDefinition as $i => $value) {
139+
foreach ($properties as $i => $value) {
136140
$property = &$this->getProperty($element, $i, $undefinedConstraint);
137-
$definition = $this->getProperty($objectDefinition, $i);
141+
$definition = $this->getProperty($properties, $i);
138142

139143
if (is_object($definition)) {
140144
// Undefined constraint will check for is_object() and quit if is not - so why pass it?

Diff for: src/JsonSchema/Constraints/SchemaConstraint.php

+6-5
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i =
3535
// passed schema
3636
$validationSchema = $schema;
3737
} elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) {
38-
$inlineSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
39-
if (is_array($inlineSchema)) {
40-
$inlineSchema = json_decode(json_encode($inlineSchema));
41-
}
4238
// inline schema
43-
$validationSchema = $inlineSchema;
39+
$validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
4440
} else {
4541
throw new InvalidArgumentException('no schema found to verify against');
4642
}
4743

44+
// cast array schemas to object
45+
if (is_array($validationSchema)) {
46+
$validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema);
47+
}
48+
4849
// validate schema against whatever is defined in $validationSchema->$schema. If no
4950
// schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04).
5051
if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) {

Diff for: src/JsonSchema/Constraints/UndefinedConstraint.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = n
7272
// is not set (i.e. don't use $this->getTypeCheck() here).
7373
$this->checkObject(
7474
$value,
75-
isset($schema->properties) ? $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties) : $schema,
75+
$schema,
7676
$path,
77+
isset($schema->properties) ? $schema->properties : null,
7778
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
7879
isset($schema->patternProperties) ? $schema->patternProperties : null,
7980
$this->appliedDefaults
@@ -251,8 +252,14 @@ protected function applyDefaultValues(&$value, $schema, $path)
251252
}
252253
}
253254
} elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
255+
$items = array();
256+
if (LooseTypeCheck::isArray($schema->items)) {
257+
$items = $schema->items;
258+
} elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
259+
$items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
260+
}
254261
// $value is an array, and items are defined - treat as plain array
255-
foreach ($schema->items as $currentItem => $itemDefinition) {
262+
foreach ($items as $currentItem => $itemDefinition) {
256263
if (
257264
!array_key_exists($currentItem, $value)
258265
&& property_exists($itemDefinition, 'default')

Diff for: src/JsonSchema/Rfc3339.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class Rfc3339
66
{
7-
const REGEX = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/';
7+
const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/';
88

99
/**
1010
* Try creating a DateTime instance
@@ -22,8 +22,8 @@ public static function createFromString($string)
2222
$dateAndTime = $matches[1];
2323
$microseconds = $matches[2] ?: '.000000';
2424
$timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00';
25-
26-
$dateTime = \DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));
25+
$dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
26+
$dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));
2727

2828
return $dateTime ?: null;
2929
}

Diff for: src/JsonSchema/SchemaStorage.php

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace JsonSchema;
44

5+
use JsonSchema\Constraints\BaseConstraint;
56
use JsonSchema\Entity\JsonPointer;
67
use JsonSchema\Exception\UnresolvableJsonPointerException;
78
use JsonSchema\Iterator\ObjectIterator;
@@ -51,6 +52,23 @@ public function addSchema($id, $schema = null)
5152
// schemas do not have an associated URI when passed via Validator::validate().
5253
$schema = $this->uriRetriever->retrieve($id);
5354
}
55+
56+
// cast array schemas to object
57+
if (is_array($schema)) {
58+
$schema = BaseConstraint::arrayToObjectRecursive($schema);
59+
}
60+
61+
// workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
62+
// see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
63+
if (is_object($schema) && property_exists($schema, 'id')) {
64+
if ($schema->id == 'http://json-schema.org/draft-04/schema#') {
65+
$schema->properties->id->format = 'uri-reference';
66+
} elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') {
67+
$schema->properties->id->format = 'uri-reference';
68+
$schema->properties->{'$ref'}->format = 'uri-reference';
69+
}
70+
}
71+
5472
$objectIterator = new ObjectIterator($schema);
5573
foreach ($objectIterator as $toResolveSchema) {
5674
if (property_exists($toResolveSchema, '$ref') && is_string($toResolveSchema->{'$ref'})) {

Diff for: src/JsonSchema/Validator.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ public function validate(&$value, $schema = null, $checkMode = null)
5757
$this->factory->getSchemaStorage()->addSchema(SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI, $schema);
5858

5959
$validator = $this->factory->createInstanceFor('schema');
60-
$validator->check($value, $schema);
60+
$validator->check(
61+
$value,
62+
$this->factory->getSchemaStorage()->getSchema(SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI)
63+
);
6164

6265
$this->factory->setConfig($initialCheckMode);
6366

Diff for: tests/Constraints/DefaultPropertiesTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,21 @@ public function getValidTests()
149149
'{"items":[{"default":null}]}',
150150
'[null]'
151151
),
152+
array(// #21 items might be a schema (instead of an array of schema)
153+
'[{}]',
154+
'{"items":{"properties":{"propertyOne":{"default":"valueOne"}}}}',
155+
'[{"propertyOne":"valueOne"}]'
156+
),
157+
array(// #22 if items is not an array, it does not create a new item
158+
'[]',
159+
'{"items":{"properties":{"propertyOne":{"default":"valueOne"}}}}',
160+
'[]'
161+
),
162+
array(// #23 if items is a schema with a default value and minItems is present, fill the array
163+
'["a"]',
164+
'{"items":{"default":"b"}, "minItems": 3}',
165+
'["a","b","b"]'
166+
),
152167
);
153168
}
154169

Diff for: tests/Constraints/FormatTest.php

+12-6
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,12 @@ public function getValidFormats()
143143
array('555 320 1212', 'phone'),
144144

145145
array('http://bluebox.org', 'uri'),
146-
array('//bluebox.org', 'uri'),
147-
array('/absolutePathReference/', 'uri'),
148-
array('./relativePathReference/', 'uri'),
149-
array('./relative:PathReference/', 'uri'),
150-
array('relativePathReference/', 'uri'),
151-
array('relative/Path:Reference/', 'uri'),
146+
array('//bluebox.org', 'uri-reference'),
147+
array('/absolutePathReference/', 'uri-reference'),
148+
array('./relativePathReference/', 'uri-reference'),
149+
array('./relative:PathReference/', 'uri-reference'),
150+
array('relativePathReference/', 'uri-reference'),
151+
array('relative/Path:Reference/', 'uri-reference'),
152152

153153
array('[email protected]', 'email'),
154154

@@ -200,6 +200,12 @@ public function getInvalidFormats()
200200
array('htt:/bluebox.org', 'uri'),
201201
array('.relative:path/reference/', 'uri'),
202202
array('', 'uri'),
203+
array('//bluebox.org', 'uri'),
204+
array('/absolutePathReference/', 'uri'),
205+
array('./relativePathReference/', 'uri'),
206+
array('./relative:PathReference/', 'uri'),
207+
array('relativePathReference/', 'uri'),
208+
array('relative/Path:Reference/', 'uri'),
203209

204210
array('info@somewhere', 'email'),
205211

Diff for: tests/Constraints/MinMaxPropertiesTest.php

+20-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function getInvalidTests()
7474
return array(
7575
array(
7676
'{
77-
"value": 1
77+
"value": {}
7878
}',
7979
'{
8080
"type": "object",
@@ -83,9 +83,27 @@ public function getInvalidTests()
8383
}
8484
}'
8585
),
86+
array(
87+
'{}',
88+
'{
89+
"type": "object",
90+
"properties": {
91+
"propertyOne": {
92+
"type": "string"
93+
},
94+
"propertyTwo": {
95+
"type": "string"
96+
}
97+
},
98+
"minProperties": 1
99+
}'
100+
),
86101
array(
87102
'{
88-
"value": 1
103+
"value": {
104+
"propertyOne": "valueOne",
105+
"propertyTwo": "valueTwo"
106+
}
89107
}',
90108
'{
91109
"type": "object",

Diff for: tests/Rfc3339Test.php

+18-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ public function provideValidFormats()
3535
'2000-05-01T12:12:12Z',
3636
\DateTime::createFromFormat('Y-m-d\TH:i:s', '2000-05-01T12:12:12', new \DateTimeZone('UTC'))
3737
),
38-
array('2000-05-01T12:12:12+0100', \DateTime::createFromFormat('Y-m-d\TH:i:sP', '2000-05-01T12:12:12+01:00')),
39-
array('2000-05-01T12:12:12+01:00', \DateTime::createFromFormat('Y-m-d\TH:i:sP', '2000-05-01T12:12:12+01:00')),
38+
array(
39+
'2000-05-01T12:12:12+0100',
40+
\DateTime::createFromFormat('Y-m-d\TH:i:sP', '2000-05-01T12:12:12+01:00')
41+
),
42+
array(
43+
'2000-05-01T12:12:12+01:00',
44+
\DateTime::createFromFormat('Y-m-d\TH:i:sP', '2000-05-01T12:12:12+01:00')
45+
),
4046
array(
4147
'2000-05-01T12:12:12.123456Z',
4248
\DateTime::createFromFormat('Y-m-d\TH:i:s.u', '2000-05-01T12:12:12.123456', new \DateTimeZone('UTC'))
@@ -45,6 +51,14 @@ public function provideValidFormats()
4551
'2000-05-01T12:12:12.123Z',
4652
\DateTime::createFromFormat('Y-m-d\TH:i:s.u', '2000-05-01T12:12:12.123000', new \DateTimeZone('UTC'))
4753
),
54+
array(
55+
'2000-05-01 12:12:12.123Z',
56+
\DateTime::createFromFormat('Y-m-d H:i:s.u', '2000-05-01 12:12:12.123000', new \DateTimeZone('UTC'))
57+
),
58+
array(
59+
'2000-05-01 12:12:12.123456Z',
60+
\DateTime::createFromFormat('Y-m-d H:i:s.u', '2000-05-01 12:12:12.123456', new \DateTimeZone('UTC'))
61+
)
4862
);
4963
}
5064

@@ -54,6 +68,8 @@ public function provideInvalidFormats()
5468
array('1999-1-11T00:00:00Z'),
5569
array('1999-01-11T00:00:00+100'),
5670
array('1999-01-11T00:00:00+1:00'),
71+
array('1999-01-01 00:00:00Z'),
72+
array('1999-1-11 00:00:00Z')
5773
);
5874
}
5975
}

0 commit comments

Comments
 (0)