Skip to content

Commit 241e147

Browse files
committed
Merge pull request #41 from mchiocca/master
Fix issues #32 and #33. Draft v3 compliance excluding $ref, $schema, and id.
2 parents 40f7272 + 65bfb0a commit 241e147

21 files changed

+1034
-239
lines changed

src/JsonSchema/Constraints/Collection.php

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,14 @@ public function check($value, $schema = null, $path = null, $i = null)
3333
}
3434

3535
// Verify uniqueItems
36-
// @TODO array_unique doesnt work with objects
37-
if (isset($schema->uniqueItems) && array_unique($value) != $value) {
38-
$this->addError($path, "There are no duplicates allowed in the array");
36+
if (isset($schema->uniqueItems)) {
37+
$unique = $value;
38+
if (is_array($value) && count($value)) {
39+
$unique = array_map(function($e) { return var_export($e, true); }, $value);
40+
}
41+
if (count(array_unique($unique)) != count($value)) {
42+
$this->addError($path, "There are no duplicates allowed in the array");
43+
}
3944
}
4045

4146
// Verify items
@@ -54,15 +59,13 @@ public function check($value, $schema = null, $path = null, $i = null)
5459
*/
5560
protected function validateItems($value, $schema = null, $path = null, $i = null)
5661
{
57-
if (!is_array($schema->items)) {
62+
if (is_object($schema->items)) {
5863
// just one type definition for the whole array
5964
foreach ($value as $k => $v) {
6065
$initErrors = $this->getErrors();
6166

6267
// First check if its defined in "items"
63-
if (!isset($schema->additionalItems) || $schema->additionalItems === false) {
64-
$this->checkUndefined($v, $schema->items, $path, $k);
65-
}
68+
$this->checkUndefined($v, $schema->items, $path, $k);
6669

6770
// Recheck with "additionalItems" if the first test fails
6871
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
@@ -71,9 +74,9 @@ protected function validateItems($value, $schema = null, $path = null, $i = null
7174
}
7275

7376
// Reset errors if needed
74-
if (isset($secondErrors) && count($secondErrors) < $this->getErrors()) {
77+
if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
7578
$this->errors = $secondErrors;
76-
} elseif (isset($secondErrors) && count($secondErrors) == count($this->getErrors())) {
79+
} else if (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
7780
$this->errors = $initErrors;
7881
}
7982
}
@@ -84,13 +87,16 @@ protected function validateItems($value, $schema = null, $path = null, $i = null
8487
$this->checkUndefined($v, $schema->items[$k], $path, $k);
8588
} else {
8689
// Additional items
87-
if (array_key_exists('additionalItems', $schema) && $schema->additionalItems !== false) {
88-
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
90+
if (property_exists($schema, 'additionalItems')) {
91+
if ($schema->additionalItems !== false) {
92+
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
93+
} else {
94+
$this->addError(
95+
$path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items');
96+
}
8997
} else {
90-
$this->addError(
91-
$path,
92-
'The item ' . $i . '[' . $k . '] is not defined in the objTypeDef and the objTypeDef does not allow additional properties'
93-
);
98+
// Should be valid against an empty schema
99+
$this->checkUndefined($v, new \stdClass(), $path, $k);
94100
}
95101
}
96102
}

src/JsonSchema/Constraints/ConstraintInterface.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ function isValid();
5050
*
5151
* @abstract
5252
* @param mixed $value
53-
* @param null $schema
54-
* @param null $path
55-
* @param null $i
53+
* @param mixed $schema
54+
* @param mixed $path
55+
* @param mixed $i
5656
*/
5757
function check($value, $schema = null, $path = null, $i = null);
5858
}

src/JsonSchema/Constraints/Enum.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ public function check($element, $schema = null, $path = null, $i = null)
2727
return;
2828
}
2929

30-
if (!in_array($element, $schema->enum)) {
31-
$this->addError($path, "does not have a value in the enumeration " . implode(', ', $schema->enum));
30+
foreach ($schema->enum as $enum) {
31+
if ((gettype($element) === gettype($enum)) && ($element == $enum)) {
32+
return;
33+
}
3234
}
35+
36+
$this->addError($path, "does not have a value in the enumeration " . print_r($schema->enum, true));
3337
}
3438
}

src/JsonSchema/Constraints/Format.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@ public function check($element, $schema = null, $path = null, $i = null)
3535

3636
case 'time':
3737
if (!$this->validateDateTime($element, 'H:i:s')) {
38-
$this->addError($path, sprintf('Invalid time %s, expected format HH:MM:SS', json_encode($element)));
38+
$this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)));
3939
}
4040
break;
4141

4242
case 'date-time':
4343
if (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z') &&
44+
!$this->validateDateTime($element, 'Y-m-d\TH:i:s.u\Z') &&
4445
!$this->validateDateTime($element, 'Y-m-d\TH:i:sP') &&
4546
!$this->validateDateTime($element, 'Y-m-d\TH:i:sO')
4647
) {
47-
$this->addError($path, sprintf('Invalid date time %s, expected format YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DDTHH:MM:SS+HH:MM', json_encode($element)));
48+
$this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)));
4849
}
4950
break;
5051

@@ -55,8 +56,8 @@ public function check($element, $schema = null, $path = null, $i = null)
5556
break;
5657

5758
case 'regex':
58-
if (!$this->validateRegex($element, $schema->pattern)) {
59-
$this->addError($path, "Invalid regex format");
59+
if (!$this->validateRegex($element)) {
60+
$this->addError($path, 'Invalid regex format ' . $element);
6061
}
6162
break;
6263

@@ -125,9 +126,9 @@ protected function validateDateTime($datetime, $format)
125126
return $datetime === $dt->format($format);
126127
}
127128

128-
protected function validateRegex($string, $regex)
129+
protected function validateRegex($regex)
129130
{
130-
return preg_match('/' . $regex . '/', $string);
131+
return false !== @preg_match('/' . $regex . '/', '');
131132
}
132133

133134
protected function validateColor($color)

src/JsonSchema/Constraints/Number.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,56 @@ class Number extends Constraint
2323
public function check($element, $schema = null, $path = null, $i = null)
2424
{
2525
// Verify minimum
26-
if (isset($schema->minimum) && $element < $schema->minimum) {
26+
if (isset($schema->exclusiveMinimum)) {
27+
if (isset($schema->minimum)) {
28+
if ($schema->exclusiveMinimum && $element === $schema->minimum) {
29+
$this->addError($path, "must have a minimum value greater than boundary value of " . $schema->minimum);
30+
} else if ($element < $schema->minimum) {
31+
$this->addError($path, "must have a minimum value of " . $schema->minimum);
32+
}
33+
} else {
34+
$this->addError($path, "use of exclusiveMinimum requires presence of minimum");
35+
}
36+
} else if (isset($schema->minimum) && $element < $schema->minimum) {
2737
$this->addError($path, "must have a minimum value of " . $schema->minimum);
2838
}
2939

3040
// Verify maximum
31-
if (isset($schema->maximum) && $element > $schema->maximum) {
41+
if (isset($schema->exclusiveMaximum)) {
42+
if (isset($schema->maximum)) {
43+
if ($schema->exclusiveMaximum && $element === $schema->maximum) {
44+
$this->addError($path, "must have a maximum value less than boundary value of " . $schema->maximum);
45+
} else if ($element > $schema->maximum) {
46+
$this->addError($path, "must have a maximum value of " . $schema->maximum);
47+
}
48+
} else {
49+
$this->addError($path, "use of exclusiveMaximum requires presence of maximum");
50+
}
51+
} else if (isset($schema->maximum) && $element > $schema->maximum) {
3252
$this->addError($path, "must have a maximum value of " . $schema->maximum);
3353
}
3454

3555
// Verify divisibleBy
36-
if (isset($schema->divisibleBy) && $element % $schema->divisibleBy != 0) {
56+
if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
3757
$this->addError($path, "is not divisible by " . $schema->divisibleBy);
3858
}
3959

4060
$this->checkFormat($element, $schema, $path, $i);
4161
}
42-
}
62+
63+
private function fmod($number1, $number2)
64+
{
65+
$modulus = fmod($number1, $number2);
66+
$precision = abs(0.0000000001);
67+
$diff = (float)($modulus - $number2);
68+
69+
if (-$precision < $diff && $diff < $precision) {
70+
return 0.0;
71+
}
72+
73+
$decimals1 = mb_strpos($number1, ".") ? mb_strlen($number1) - mb_strpos($number1, ".") - 1 : 0;
74+
$decimals2 = mb_strpos($number2, ".") ? mb_strlen($number2) - mb_strpos($number2, ".") - 1 : 0;
75+
76+
return (float)round($modulus, max($decimals1, $decimals2));
77+
}
78+
}

src/JsonSchema/Constraints/Type.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ protected function validateType($value, $type)
7878
}
7979

8080
if ('integer' === $type) {
81-
return is_int($value) || ctype_digit($value);
81+
return is_int($value);
8282
}
8383

8484
if ('number' === $type) {
85-
return is_numeric($value);
85+
return is_numeric($value) && !is_string($value);
8686
}
8787

8888
if ('boolean' === $type) {

src/JsonSchema/Constraints/Undefined.php

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
namespace JsonSchema\Constraints;
1111

12-
use JsonSchema\Validator;
13-
1412
/**
1513
* The Undefined Constraints
1614
*
@@ -40,10 +38,10 @@ public function check($value, $schema = null, $path = null, $i = null)
4038
/**
4139
* Validates the value against the types
4240
*
43-
* @param $value
44-
* @param null $schema
45-
* @param null $path
46-
* @param null $i
41+
* @param mixed $value
42+
* @param mixed $schema
43+
* @param string $path
44+
* @param string $i
4745
*/
4846
public function validateTypes($value, $schema = null, $path = null, $i = null)
4947
{
@@ -82,25 +80,44 @@ public function validateTypes($value, $schema = null, $path = null, $i = null)
8280
/**
8381
* Validates common properties
8482
*
85-
* @param $value
86-
* @param null $schema
87-
* @param null $path
88-
* @param null $i
83+
* @param mixed $value
84+
* @param mixed $schema
85+
* @param string $path
86+
* @param string $i
8987
*/
9088
protected function validateCommonProperties($value, $schema = null, $path = null, $i = null)
9189
{
9290
// if it extends another schema, it must pass that schema as well
9391
if (isset($schema->extends)) {
9492
if (is_string($schema->extends)) {
95-
$schema->extends = $this->validateUri($schema->extends, $schema, $path, $i);
93+
$schema->extends = $this->validateUri($schema, $schema->extends);
94+
}
95+
$increment = is_null($i) ? "" : $i;
96+
if (is_array($schema->extends)) {
97+
foreach ($schema->extends as $extends) {
98+
$this->checkUndefined($value, $extends, $path, $increment);
99+
}
100+
} else {
101+
$this->checkUndefined($value, $schema->extends, $path, $increment);
96102
}
97-
$this->checkUndefined($value, $schema->extends, $path, $i);
98103
}
99104

100105
// Verify required values
101-
if (is_object($value) && $value instanceof Undefined) {
102-
if (isset($schema->required) && $schema->required) {
103-
$this->addError($path, "is missing and it is required");
106+
if (is_object($value)) {
107+
if ($value instanceof Undefined) {
108+
// Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
109+
if (isset($schema->required) && $schema->required) {
110+
$this->addError($path, "is missing and it is required");
111+
}
112+
} else if (isset($schema->required)) {
113+
// Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
114+
foreach ($schema->required as $required) {
115+
if (!property_exists($value, $required)) {
116+
$this->addError($path, "the property " . $required . " is required");
117+
}
118+
}
119+
} else {
120+
$this->checkType($value, $schema, $path);
104121
}
105122
} else {
106123
$this->checkType($value, $schema, $path);
@@ -110,7 +127,9 @@ protected function validateCommonProperties($value, $schema = null, $path = null
110127
if (isset($schema->disallow)) {
111128
$initErrors = $this->getErrors();
112129

113-
$this->checkUndefined($value, $schema->disallow, $path);
130+
$typeSchema = new \stdClass();
131+
$typeSchema->type = $schema->disallow;
132+
$this->checkType($value, $typeSchema, $path);
114133

115134
// if no new errors were raised it must be a disallowed value
116135
if (count($this->getErrors()) == count($initErrors)) {
@@ -119,9 +138,45 @@ protected function validateCommonProperties($value, $schema = null, $path = null
119138
$this->errors = $initErrors;
120139
}
121140
}
141+
142+
// Verify that dependencies are met
143+
if (is_object($value) && isset($schema->dependencies)) {
144+
$this->validateDependencies($value, $schema->dependencies, $path);
145+
}
146+
}
147+
148+
/**
149+
* Validate dependencies
150+
*
151+
* @param mixed $value
152+
* @param mixed $dependencies
153+
* @param string $path
154+
*/
155+
protected function validateDependencies($value, $dependencies, $path)
156+
{
157+
foreach ($dependencies as $key => $dependency) {
158+
if (property_exists($value, $key)) {
159+
if (is_string($dependency)) {
160+
// Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
161+
if (!property_exists($value, $dependency)) {
162+
$this->addError($path, "$key depends on $dependency and $dependency is missing");
163+
}
164+
} else if (is_array($dependency)) {
165+
// Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
166+
foreach ($dependency as $d) {
167+
if (!property_exists($value, $d)) {
168+
$this->addError($path, "$key depends on $d and $d is missing");
169+
}
170+
}
171+
} else if (is_object($dependency)) {
172+
// Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
173+
$this->checkUndefined($value, $dependency, $path, "");
174+
}
175+
}
176+
}
122177
}
123178

124-
protected function validateUri($schemaUri = null, $schema, $path = null, $i = null)
179+
protected function validateUri($schema, $schemaUri = null)
125180
{
126181
$resolver = new \JsonSchema\Uri\UriResolver();
127182
$retriever = $this->getUriRetriever();

0 commit comments

Comments
 (0)