From 57a88c2aeb5bd280f7034d54b3f6833c82bf1e49 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Tue, 30 Apr 2013 13:02:14 -0700 Subject: [PATCH 01/13] Fix a number of miscellaneous draft v3 bugs and add new unit tests. --- src/JsonSchema/Constraints/Collection.php | 11 ++-- src/JsonSchema/Constraints/Enum.php | 8 ++- src/JsonSchema/Constraints/Format.php | 17 +++--- src/JsonSchema/Constraints/Number.php | 2 +- .../Tests/Constraints/DivisibleByTest.php | 44 ++++++++++++--- .../JsonSchema/Tests/Constraints/EnumTest.php | 53 +++++++++++++++++++ .../Tests/Constraints/FormatTest.php | 3 +- .../Tests/Constraints/PatternTest.php | 20 +++++++ .../Tests/Constraints/UniqueItemsTest.php | 18 ++++++- 9 files changed, 154 insertions(+), 22 deletions(-) diff --git a/src/JsonSchema/Constraints/Collection.php b/src/JsonSchema/Constraints/Collection.php index 311869d5..595f88da 100644 --- a/src/JsonSchema/Constraints/Collection.php +++ b/src/JsonSchema/Constraints/Collection.php @@ -33,9 +33,14 @@ public function check($value, $schema = null, $path = null, $i = null) } // Verify uniqueItems - // @TODO array_unique doesnt work with objects - if (isset($schema->uniqueItems) && array_unique($value) != $value) { - $this->addError($path, "There are no duplicates allowed in the array"); + if (isset($schema->uniqueItems)) { + $unique = $value; + if (is_array($value) && count($value)) { + $unique = array_map(function($e) { return print_r($e, true); }, $value); + } + if (count(array_unique($unique)) != count($value)) { + $this->addError($path, "There are no duplicates allowed in the array"); + } } // Verify items diff --git a/src/JsonSchema/Constraints/Enum.php b/src/JsonSchema/Constraints/Enum.php index a4ec8944..c80a2d55 100644 --- a/src/JsonSchema/Constraints/Enum.php +++ b/src/JsonSchema/Constraints/Enum.php @@ -27,8 +27,12 @@ public function check($element, $schema = null, $path = null, $i = null) return; } - if (!in_array($element, $schema->enum)) { - $this->addError($path, "does not have a value in the enumeration " . implode(', ', $schema->enum)); + foreach ($schema->enum as $enum) { + if ((gettype($element) === gettype($enum)) && ($element == $enum)) { + return; + } } + + $this->addError($path, "does not have a value in the enumeration " . print_r($schema->enum, true)); } } \ No newline at end of file diff --git a/src/JsonSchema/Constraints/Format.php b/src/JsonSchema/Constraints/Format.php index acc555f2..7468f65c 100644 --- a/src/JsonSchema/Constraints/Format.php +++ b/src/JsonSchema/Constraints/Format.php @@ -35,16 +35,17 @@ public function check($element, $schema = null, $path = null, $i = null) case 'time': if (!$this->validateDateTime($element, 'H:i:s')) { - $this->addError($path, sprintf('Invalid time %s, expected format HH:MM:SS', json_encode($element))); + $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element))); } break; case 'date-time': if (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z') && + !$this->validateDateTime($element, 'Y-m-d\TH:i:s.u\Z') && !$this->validateDateTime($element, 'Y-m-d\TH:i:sP') && !$this->validateDateTime($element, 'Y-m-d\TH:i:sO') ) { - $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))); + $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))); } break; @@ -55,9 +56,7 @@ public function check($element, $schema = null, $path = null, $i = null) break; case 'regex': - if (!$this->validateRegex($element, $schema->pattern)) { - $this->addError($path, "Invalid regex format"); - } + $this->validateRegex($path, $element, $schema->pattern); break; case 'color': @@ -125,9 +124,13 @@ protected function validateDateTime($datetime, $format) return $datetime === $dt->format($format); } - protected function validateRegex($string, $regex) + protected function validateRegex($path, $element, $regex) { - return preg_match('/' . $regex . '/', $string); + if (false === @preg_match('/' . $regex . '/', '')) { + $this->addError($path, 'Invalid regex format ' . $regex); + } else if (!preg_match('/' . $regex . '/', $element)) { + $this->addError($path, sprintf('%s does not match regex format %s', json_encode($element), $regex)); + } } protected function validateColor($color) diff --git a/src/JsonSchema/Constraints/Number.php b/src/JsonSchema/Constraints/Number.php index 74a3111c..2e765e12 100644 --- a/src/JsonSchema/Constraints/Number.php +++ b/src/JsonSchema/Constraints/Number.php @@ -33,7 +33,7 @@ public function check($element, $schema = null, $path = null, $i = null) } // Verify divisibleBy - if (isset($schema->divisibleBy) && $element % $schema->divisibleBy != 0) { + if (isset($schema->divisibleBy) && fmod($element, $schema->divisibleBy) != 0) { $this->addError($path, "is not divisible by " . $schema->divisibleBy); } diff --git a/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php b/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php index 895b7639..ba77c6aa 100644 --- a/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php +++ b/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php @@ -15,15 +15,40 @@ public function getInvalidTests() { return array( array( - '{ - "value":5.6333 - }', + '{"value": 5.6333}', '{ "type":"object", "properties":{ "value":{"type":"number","divisibleBy":3} } }' + ), + array( + '{"value": 35}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "divisibleBy": 1.5} + } + }' + ), + array( + '{"value": 0.00751}', + '{ + "type": "object", + "properties": { + "value": {"type": "number", "divisibleBy": 0.0001} + } + }' + ), + array( + '{"value": 7}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "divisibleBy": 2} + } + }' ) ); } @@ -32,15 +57,22 @@ public function getValidTests() { return array( array( - '{ - "value":6 - }', + '{"value": 6}', '{ "type":"object", "properties":{ "value":{"type":"number","divisibleBy":3} } }' + ), + array( + '{"value": 4.5}', + '{ + "type": "object", + "properties": { + "value": {"type": "number", "divisibleBy": 1.5} + } + }' ) ); } diff --git a/tests/JsonSchema/Tests/Constraints/EnumTest.php b/tests/JsonSchema/Tests/Constraints/EnumTest.php index 64187d17..0ca5b9e2 100644 --- a/tests/JsonSchema/Tests/Constraints/EnumTest.php +++ b/tests/JsonSchema/Tests/Constraints/EnumTest.php @@ -39,6 +39,30 @@ public function getInvalidTests() }, "additionalProperties":false }' + ), + array( + '{"value": 4}', + '{ + "type": "object", + "properties": { + "value": { + "type": "integer", "enum": [1, 2, 3] + } + }, + "additionalProperties": false + }' + ), + array( + '{"value": {"foo": false}}', + '{ + "type": "object", + "properties": { + "value": { + "type": "any", "enum": [6, "foo", [], true, {"foo": 12}] + } + }, + "additionalProperties": false + }' ) ); } @@ -81,6 +105,35 @@ public function getValidTests() }, "additionalProperties":false }' + ), + array( + '{"value": 1}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "enum": [1, 2, 3]} + } + }' + ), + array( + '{"value": []}', + '{ + "type": "object", + "properties": { + "value": {"type": "any", "enum": [6, "foo", [], true, {"foo": 12}]} + }, + "additionalProperties": false + }' + ), + array( + '{"value": {"foo": 12}}', + '{ + "type": "object", + "properties": { + "value": {"type": "any", "enum": [6, "foo", [], true, {"foo": 12}]} + }, + "additionalProperties": false + }' ) ); } diff --git a/tests/JsonSchema/Tests/Constraints/FormatTest.php b/tests/JsonSchema/Tests/Constraints/FormatTest.php index 537c9711..2f2e1bec 100644 --- a/tests/JsonSchema/Tests/Constraints/FormatTest.php +++ b/tests/JsonSchema/Tests/Constraints/FormatTest.php @@ -73,8 +73,9 @@ public function getValidFormats() array('23:59:59', 'time'), array('2000-05-01T12:12:12Z', 'date-time'), - array('2000-05-01T12:12:12+0100', 'date-time'), + array('2000-05-01T12:12:12+0100', 'date-time'), array('2000-05-01T12:12:12+01:00', 'date-time'), + array('2000-05-01T12:12:12.123456Z', 'date-time'), array('0', 'utc-millisec'), diff --git a/tests/JsonSchema/Tests/Constraints/PatternTest.php b/tests/JsonSchema/Tests/Constraints/PatternTest.php index 2a18fc93..f28a5790 100644 --- a/tests/JsonSchema/Tests/Constraints/PatternTest.php +++ b/tests/JsonSchema/Tests/Constraints/PatternTest.php @@ -25,6 +25,16 @@ public function getInvalidTests() }, "additionalProperties":false }' + ), + array( + '{"value": "abc"}', + '{ + "type": "object", + "properties": { + "value": {"type": "string", "pattern": "^a*$"} + }, + "additionalProperties": false + }' ) ); } @@ -55,6 +65,16 @@ public function getValidTests() }, "additionalProperties":false }' + ), + array( + '{"value": "aaa"}', + '{ + "type": "object", + "properties": { + "value": {"type": "string", "pattern": "^a*$"} + }, + "additionalProperties": false + }' ) ); } diff --git a/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php b/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php index 24e6e22b..1772e9e5 100644 --- a/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php +++ b/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php @@ -21,13 +21,20 @@ public function getInvalidTests() "uniqueItems": true }' ), -/* array( + array( '[{"a":"b"},{"a":"c"},{"a":"b"}]', '{ "type":"array", "uniqueItems": true }' - )*/ + ), + array( + '[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : true}}}]', + '{ + "type": "array", + "uniqueItems": true + }' + ) ); } @@ -40,6 +47,13 @@ public function getValidTests() "type":"array", "uniqueItems": true }' + ), + array( + '[{"foo": 12}, {"bar": false}]', + '{ + "type": "array", + "uniqueItems": true + }' ) ); } From 1f1120ecc8765e35c96e0bd5b0dfd27217c595d8 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Tue, 30 Apr 2013 17:22:05 -0700 Subject: [PATCH 02/13] Fix issue #32: required properties ignored including new unit tests --- src/JsonSchema/Constraints/Constraint.php | 6 +- src/JsonSchema/Constraints/Object.php | 17 ++- src/JsonSchema/Constraints/Undefined.php | 3 +- .../Constraints/RequiredPropertyTest.php | 113 +++++++++++++++--- 4 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/JsonSchema/Constraints/Constraint.php b/src/JsonSchema/Constraints/Constraint.php index b3cdcd88..b946e800 100644 --- a/src/JsonSchema/Constraints/Constraint.php +++ b/src/JsonSchema/Constraints/Constraint.php @@ -151,11 +151,13 @@ protected function checkArray($value, $schema = null, $path = null, $i = null) * @param mixed $path * @param mixed $i * @param mixed $patternProperties + * @param mixed $requiredProp */ - protected function checkObject($value, $schema = null, $path = null, $i = null, $patternProperties = null) + protected function checkObject( + $value, $schema = null, $path = null, $i = null, $patternProperties = null, $requiredProp = null) { $validator = new Object($this->checkMode, $this->uriRetriever); - $validator->check($value, $schema, $path, $i, $patternProperties); + $validator->check($value, $schema, $path, $i, $patternProperties, $requiredProp); $this->addErrors($validator->getErrors()); } diff --git a/src/JsonSchema/Constraints/Object.php b/src/JsonSchema/Constraints/Object.php index 0d48b8b9..fad49ec5 100644 --- a/src/JsonSchema/Constraints/Object.php +++ b/src/JsonSchema/Constraints/Object.php @@ -20,7 +20,9 @@ class Object extends Constraint /** * {@inheritDoc} */ - function check($element, $definition = null, $path = null, $additionalProp = null, $patternProperties = null) + function check( + $element, $definition = null, + $path = null, $additionalProp = null, $patternProperties = null, $requiredProp = null) { if ($element instanceof Undefined) { return; @@ -33,7 +35,7 @@ function check($element, $definition = null, $path = null, $additionalProp = nul if ($definition) { // validate the definition properties - $this->validateDefinition($element, $definition, $path); + $this->validateDefinition($element, $definition, $requiredProp, $path); } // additional the element properties @@ -106,15 +108,24 @@ public function validateElement($element, $matches, $objectDefinition = null, $p * * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition Object definition + * @param array $requiredProp Required properties * @param string $path Path? */ - public function validateDefinition($element, $objectDefinition = null, $path = null) + public function validateDefinition($element, $objectDefinition = null, $requiredProp = null, $path = null) { foreach ($objectDefinition as $i => $value) { $property = $this->getProperty($element, $i, new Undefined()); $definition = $this->getProperty($objectDefinition, $i); $this->checkUndefined($property, $definition, $path, $i); } + + if ($requiredProp) { + foreach ($requiredProp as $required) { + if (!$this->getProperty($element, $required)) { + $this->addError($path, "the property " . $required . " is required"); + } + } + } } /** diff --git a/src/JsonSchema/Constraints/Undefined.php b/src/JsonSchema/Constraints/Undefined.php index 8e243aea..ea22d4fb 100644 --- a/src/JsonSchema/Constraints/Undefined.php +++ b/src/JsonSchema/Constraints/Undefined.php @@ -59,7 +59,8 @@ public function validateTypes($value, $schema = null, $path = null, $i = null) isset($schema->properties) ? $schema->properties : null, $path, isset($schema->additionalProperties) ? $schema->additionalProperties : null, - isset($schema->patternProperties) ? $schema->patternProperties : null + isset($schema->patternProperties) ? $schema->patternProperties : null, + isset($schema->required) ? $schema->required : null ); } diff --git a/tests/JsonSchema/Tests/Constraints/RequiredPropertyTest.php b/tests/JsonSchema/Tests/Constraints/RequiredPropertyTest.php index 6e46444b..a2d8d771 100644 --- a/tests/JsonSchema/Tests/Constraints/RequiredPropertyTest.php +++ b/tests/JsonSchema/Tests/Constraints/RequiredPropertyTest.php @@ -19,9 +19,49 @@ public function getInvalidTests() '{ "type":"object", "properties":{ - "number":{"type":"string","required":true} + "number":{"type":"number","required":true} } }' + ), + array( + '{}', + '{ + "type": "object", + "properties": { + "number": {"type": "number"} + }, + "required": ["number"] + }' + ), + array( + '{ + "foo": {} + }', + '{ + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": {"type": "number"} + }, + "required": ["bar"] + } + } + }' + ), + array( + '{ + "bar": 1.4 + }', + '{ + "type": "object", + "properties": { + "foo": {"type": "string", "required": true}, + "bar": {"type": "number"} + }, + "required": ["bar"] + }' ) ); } @@ -31,12 +71,12 @@ public function getValidTests() return array( array( '{ - "number": "1.4" + "number": 1.4 }', '{ "type":"object", "properties":{ - "number":{"type":"string","required":true} + "number":{"type":"number","required":true} } }' ), @@ -45,14 +85,23 @@ public function getValidTests() '{ "type":"object", "properties":{ - "number":{"type":"string"} + "number":{"type":"number"} } }' ), - array( + array( + '{}', '{ - "number": 0 - }', + "type":"object", + "properties":{ + "number":{"type":"number","required":false} + } + }' + ), + array( + '{ + "number": 0 + }', '{ "type":"object", "properties":{ @@ -60,10 +109,10 @@ public function getValidTests() } }' ), - array( + array( '{ - "is_active": false - }', + "is_active": false + }', '{ "type":"object", "properties":{ @@ -71,10 +120,10 @@ public function getValidTests() } }' ), - array( + array( '{ - "status": null - }', + "status": null + }', '{ "type":"object", "properties":{ @@ -82,16 +131,48 @@ public function getValidTests() } }' ), - array( + array( '{ - "users": [] - }', + "users": [] + }', '{ "type":"object", "properties":{ "users":{"type":"array","required":true} } }' + ), + array( + '{ + "foo": "foo", + "bar": 1.4 + }', + '{ + "type": "object", + "properties": { + "foo": {"type": "string", "required": true}, + "bar": {"type": "number"} + }, + "required": ["bar"] + }' + ), + array( + '{ + "foo": {"bar": 1.5} + }', + '{ + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": {"type": "number"} + }, + "required": ["bar"] + } + }, + "required": ["foo"] + }' ) ); } From 4f39913653191fa64b36d010c6f8192814fc1263 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Wed, 1 May 2013 14:16:30 -0700 Subject: [PATCH 03/13] Fix array types (items, additionalItems), and tuple typing. Add unit tests. --- src/JsonSchema/Constraints/Collection.php | 25 +++--- .../Tests/Constraints/ArraysTest.php | 77 +++++++++++++++++++ .../Tests/Constraints/TupleTypingTest.php | 56 ++++++++++---- 3 files changed, 130 insertions(+), 28 deletions(-) diff --git a/src/JsonSchema/Constraints/Collection.php b/src/JsonSchema/Constraints/Collection.php index 595f88da..3565aafc 100644 --- a/src/JsonSchema/Constraints/Collection.php +++ b/src/JsonSchema/Constraints/Collection.php @@ -59,15 +59,13 @@ public function check($value, $schema = null, $path = null, $i = null) */ protected function validateItems($value, $schema = null, $path = null, $i = null) { - if (!is_array($schema->items)) { + if (is_object($schema->items)) { // just one type definition for the whole array foreach ($value as $k => $v) { $initErrors = $this->getErrors(); // First check if its defined in "items" - if (!isset($schema->additionalItems) || $schema->additionalItems === false) { - $this->checkUndefined($v, $schema->items, $path, $k); - } + $this->checkUndefined($v, $schema->items, $path, $k); // Recheck with "additionalItems" if the first test fails if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) { @@ -76,9 +74,9 @@ protected function validateItems($value, $schema = null, $path = null, $i = null } // Reset errors if needed - if (isset($secondErrors) && count($secondErrors) < $this->getErrors()) { + if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) { $this->errors = $secondErrors; - } elseif (isset($secondErrors) && count($secondErrors) == count($this->getErrors())) { + } else if (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { $this->errors = $initErrors; } } @@ -89,13 +87,16 @@ protected function validateItems($value, $schema = null, $path = null, $i = null $this->checkUndefined($v, $schema->items[$k], $path, $k); } else { // Additional items - if (array_key_exists('additionalItems', $schema) && $schema->additionalItems !== false) { - $this->checkUndefined($v, $schema->additionalItems, $path, $k); + if (property_exists($schema, 'additionalItems')) { + if ($schema->additionalItems !== false) { + $this->checkUndefined($v, $schema->additionalItems, $path, $k); + } else { + $this->addError( + $path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items'); + } } else { - $this->addError( - $path, - 'The item ' . $i . '[' . $k . '] is not defined in the objTypeDef and the objTypeDef does not allow additional properties' - ); + // Should be valid against an empty schema + $this->checkUndefined($v, new \stdClass(), $path, $k); } } } diff --git a/tests/JsonSchema/Tests/Constraints/ArraysTest.php b/tests/JsonSchema/Tests/Constraints/ArraysTest.php index 18fc4134..1fef244c 100644 --- a/tests/JsonSchema/Tests/Constraints/ArraysTest.php +++ b/tests/JsonSchema/Tests/Constraints/ArraysTest.php @@ -28,6 +28,21 @@ public function getInvalidTests() } }' ), + array( + '{ + "array":[1,2,"a"] + }', + '{ + "type":"object", + "properties":{ + "array":{ + "type":"array", + "items":{"type":"number"}, + "additionalItems":{"type":"boolean"} + } + } + }' + ), array( '{ "array":[1,2,null] @@ -41,6 +56,19 @@ public function getInvalidTests() } } }' + ), + array( + '{"data": [1, 2, 3, "foo"]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [], + "additionalItems": {"type": "integer"} + } + } + }' ) ); } @@ -74,6 +102,55 @@ public function getValidTests() } }' ), + array( + '{"data": [1, 2, 3, 4]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [], + "additionalItems": {"type": "integer"} + } + } + }' + ), + array( + '{"data": [1, "foo", false]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [] + } + } + }' + ), + array( + '{"data": [1, "foo", false]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": {} + } + } + }' + ), + array( + '{"data": [1, 2, 3, 4, 5]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "additionalItems": false + } + } + }' + ) ); } } diff --git a/tests/JsonSchema/Tests/Constraints/TupleTypingTest.php b/tests/JsonSchema/Tests/Constraints/TupleTypingTest.php index 5488d247..ceab8ec6 100644 --- a/tests/JsonSchema/Tests/Constraints/TupleTypingTest.php +++ b/tests/JsonSchema/Tests/Constraints/TupleTypingTest.php @@ -33,7 +33,7 @@ public function getInvalidTests() ), array( '{ - "tupleTyping":["2",2,3] + "tupleTyping":["2",2,true] }', '{ "type":"object", @@ -44,7 +44,7 @@ public function getInvalidTests() {"type":"string"}, {"type":"number"} ] , - "additionalProperties":false + "additionalItems":false } } }' @@ -62,14 +62,33 @@ public function getInvalidTests() {"type":"string"}, {"type":"number"} ] , - "additionalProperties":{"type":"string"} + "additionalItems":{"type":"string"} } } }' ), + array( + '{"data": [1, "foo", true, 1.5]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [{}, {}, {}], + "additionalItems": false + } + } + }' + ) + ); + } + + public function getValidTests() + { + return array( array( '{ - "tupleTyping":["2"] + "tupleTyping":["2", 1] }', '{ "type":"object", @@ -78,22 +97,15 @@ public function getInvalidTests() "type":"array", "items":[ {"type":"string"}, - {"type":"number"}, - {"required":true} + {"type":"number"} ] } } }' - ) - ); - } - - public function getValidTests() - { - return array( + ), array( '{ - "tupleTyping":["2"] + "tupleTyping":["2",2,3] }', '{ "type":"object", @@ -102,12 +114,24 @@ public function getValidTests() "type":"array", "items":[ {"type":"string"}, - {"type":"number","required":false}, - {"type":"number","required":false} + {"type":"number"} ] } } }' + ), + array( + '{"data": [1, "foo", true]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [{}, {}, {}], + "additionalItems": false + } + } + }' ) ); } From 44c8fb61ebedfc56adf9651cdca9f6ed635c7f8d Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Tue, 7 May 2013 02:35:41 -0700 Subject: [PATCH 04/13] Fix issue #33: Strict type checking for number and integer types. --- src/JsonSchema/Constraints/Type.php | 4 +- .../Constraints/NumberAndIntegerTypesTest.php | 72 ++++++++++-- .../Tests/Constraints/PhpTypeCastModeTest.php | 104 ------------------ 3 files changed, 64 insertions(+), 116 deletions(-) delete mode 100644 tests/JsonSchema/Tests/Constraints/PhpTypeCastModeTest.php diff --git a/src/JsonSchema/Constraints/Type.php b/src/JsonSchema/Constraints/Type.php index 452f3249..fe7c0ec4 100644 --- a/src/JsonSchema/Constraints/Type.php +++ b/src/JsonSchema/Constraints/Type.php @@ -78,11 +78,11 @@ protected function validateType($value, $type) } if ('integer' === $type) { - return is_int($value) || ctype_digit($value); + return is_int($value); } if ('number' === $type) { - return is_numeric($value); + return is_numeric($value) && !is_string($value); } if ('boolean' === $type) { diff --git a/tests/JsonSchema/Tests/Constraints/NumberAndIntegerTypesTest.php b/tests/JsonSchema/Tests/Constraints/NumberAndIntegerTypesTest.php index 6f66117e..932c2f56 100644 --- a/tests/JsonSchema/Tests/Constraints/NumberAndIntegerTypesTest.php +++ b/tests/JsonSchema/Tests/Constraints/NumberAndIntegerTypesTest.php @@ -16,14 +16,59 @@ public function getInvalidTests() return array( array( '{ - "number": 1.4 + "integer": 1.4 }', '{ "type":"object", "properties":{ - "number":{"type":"integer"} + "integer":{"type":"integer"} } }' + ), + array( + '{"number": "1.5"}', + '{ + "type": "object", + "properties": { + "number": {"type": "number"} + } + }' + ), + array( + '{"integer": "1"}', + '{ + "type": "object", + "properties": { + "integer": {"type": "integer"} + } + }' + ), + array( + '{"integer": 1.001}', + '{ + "type": "object", + "properties": { + "integer": {"type": "integer"} + } + }' + ), + array( + '{"integer": true}', + '{ + "type": "object", + "properties": { + "integer": {"type": "integer"} + } + }' + ), + array( + '{"number": "x"}', + '{ + "type": "object", + "properties": { + "number": {"type": "number"} + } + }' ) ); } @@ -33,12 +78,12 @@ public function getValidTests() return array( array( '{ - "number": 1 + "integer": 1 }', '{ "type":"object", "properties":{ - "number":{"type":"number"} + "integer":{"type":"integer"} } }' ), @@ -54,14 +99,21 @@ public function getValidTests() }' ), array( + '{"number": 1e5}', '{ - "number": "1.4" - }', + "type": "object", + "properties": { + "number": {"type": "number"} + } + }' + ), + array( + '{"number": 1}', '{ - "type":"object", - "properties":{ - "number":{"type":"number"} - } + "type": "object", + "properties": { + "number": {"type": "number"} + } }' ) ); diff --git a/tests/JsonSchema/Tests/Constraints/PhpTypeCastModeTest.php b/tests/JsonSchema/Tests/Constraints/PhpTypeCastModeTest.php deleted file mode 100644 index de49dd89..00000000 --- a/tests/JsonSchema/Tests/Constraints/PhpTypeCastModeTest.php +++ /dev/null @@ -1,104 +0,0 @@ - 'a', - 'message' => 'string value found, but a number is required' - ) - ) - ), - array( - '{ - "a":"9" - }', - '{ - "type":"object", - "properties":{ - "a":{"type":"integer","maximum":"8"} - } - }' - ) - ); - } - - public function getValidTests() - { - return array( - array( - '{ - "a":"7" - }', - '{ - "type":"object", - "properties":{ - "a":{"type":"integer","maximum":8} - } - }', - Validator::CHECK_MODE_TYPE_CAST - ), - array( - '{ - "a":1.337 - }', - '{ - "type":"object", - "properties":{ - "a":{"type":"number","maximum":8.0} - } - }', - Validator::CHECK_MODE_TYPE_CAST - ), - array( - '{ - "a":"1.337" - }', - '{ - "type":"object", - "properties":{ - "a":{"type":"number","maximum":8.0} - } - }', - Validator::CHECK_MODE_TYPE_CAST - ), - array( - '{ - "a":"9e42" - }', - '{ - "type":"object", - "properties":{ - "a":{"type":"number"} - } - }', - Validator::CHECK_MODE_TYPE_CAST - ), - ); - } -} From e09aa0b7a3e63d6b6042ed73c1e30dc46ea0ee28 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Tue, 7 May 2013 05:01:32 -0700 Subject: [PATCH 05/13] Implement support for exclusiveMinimum and exclusiveMaximum. --- src/JsonSchema/Constraints/Number.php | 24 ++++- .../Tests/Constraints/MinimumMaximumTest.php | 89 +++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/JsonSchema/Constraints/Number.php b/src/JsonSchema/Constraints/Number.php index 2e765e12..4750aee3 100644 --- a/src/JsonSchema/Constraints/Number.php +++ b/src/JsonSchema/Constraints/Number.php @@ -23,12 +23,32 @@ class Number extends Constraint public function check($element, $schema = null, $path = null, $i = null) { // Verify minimum - if (isset($schema->minimum) && $element < $schema->minimum) { + if (isset($schema->exclusiveMinimum)) { + if (isset($schema->minimum)) { + if ($schema->exclusiveMinimum && $element === $schema->minimum) { + $this->addError($path, "must have a minimum value greater than boundary value of " . $schema->minimum); + } else if ($element < $schema->minimum) { + $this->addError($path, "must have a minimum value of " . $schema->minimum); + } + } else { + $this->addError($path, "use of exclusiveMinimum requires presence of minimum"); + } + } else if (isset($schema->minimum) && $element < $schema->minimum) { $this->addError($path, "must have a minimum value of " . $schema->minimum); } // Verify maximum - if (isset($schema->maximum) && $element > $schema->maximum) { + if (isset($schema->exclusiveMaximum)) { + if (isset($schema->maximum)) { + if ($schema->exclusiveMaximum && $element === $schema->maximum) { + $this->addError($path, "must have a maximum value less than boundary value of " . $schema->maximum); + } else if ($element > $schema->maximum) { + $this->addError($path, "must have a maximum value of " . $schema->maximum); + } + } else { + $this->addError($path, "use of exclusiveMaximum requires presence of maximum"); + } + } else if (isset($schema->maximum) && $element > $schema->maximum) { $this->addError($path, "must have a maximum value of " . $schema->maximum); } diff --git a/tests/JsonSchema/Tests/Constraints/MinimumMaximumTest.php b/tests/JsonSchema/Tests/Constraints/MinimumMaximumTest.php index 107ab69f..7cc3f20e 100644 --- a/tests/JsonSchema/Tests/Constraints/MinimumMaximumTest.php +++ b/tests/JsonSchema/Tests/Constraints/MinimumMaximumTest.php @@ -25,6 +25,15 @@ public function getInvalidTests() } }' ), + array( + '{"value": 3}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "minimum": 3, "exclusiveMinimum": true} + } + }' + ), array( '{ "value":16 @@ -35,6 +44,50 @@ public function getInvalidTests() "value":{"type":"integer","maximum":8} } }' + ), + array( + '{"value": 8}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "maximum": 8, "exclusiveMaximum": true} + } + }' + ), + array( + '{"value": 4}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "exclusiveMinimum": true} + } + }' + ), + array( + '{"value": 4}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "exclusiveMaximum": true} + } + }' + ), + array( + '{"value": 4}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "minimum": 5, "exclusiveMinimum": false} + } + }' + ), + array( + '{"value": 4}', + '{ + "properties": { + "value": {"type": "integer", "maximum": 3, "exclusiveMaximum": false} + } + }' ) ); } @@ -63,6 +116,42 @@ public function getValidTests() "value":{"type":"integer","maximum":8} } }' + ), + array( + '{"value": 6}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "minimum": 6, "exclusiveMinimum": false} + } + }' + ), + array( + '{"value": 6}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "maximum": 6, "exclusiveMaximum": false} + } + }' + ), + array( + '{"value": 6}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "minimum": 6} + } + }' + ), + array( + '{"value": 6}', + '{ + "type": "object", + "properties": { + "value": {"type": "integer", "maximum": 6} + } + }' ) ); } From c230cad5f25ff27dc06377840082cacc05463de8 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Tue, 7 May 2013 07:10:04 -0700 Subject: [PATCH 06/13] Fix issues with uniqueItems in arrays. Use var_export() instead of print_r(). --- src/JsonSchema/Constraints/Collection.php | 2 +- .../Tests/Constraints/UniqueItemsTest.php | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/JsonSchema/Constraints/Collection.php b/src/JsonSchema/Constraints/Collection.php index 3565aafc..4ca8e1b3 100644 --- a/src/JsonSchema/Constraints/Collection.php +++ b/src/JsonSchema/Constraints/Collection.php @@ -36,7 +36,7 @@ public function check($value, $schema = null, $path = null, $i = null) if (isset($schema->uniqueItems)) { $unique = $value; if (is_array($value) && count($value)) { - $unique = array_map(function($e) { return print_r($e, true); }, $value); + $unique = array_map(function($e) { return var_export($e, true); }, $value); } if (count(array_unique($unique)) != count($value)) { $this->addError($path, "There are no duplicates allowed in the array"); diff --git a/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php b/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php index 1772e9e5..3006bb43 100644 --- a/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php +++ b/tests/JsonSchema/Tests/Constraints/UniqueItemsTest.php @@ -34,6 +34,27 @@ public function getInvalidTests() "type": "array", "uniqueItems": true }' + ), + array( + '[1.0, 1.00, 1]', + '{ + "type": "array", + "uniqueItems": true + }' + ), + array( + '[["foo"], ["foo"]]', + '{ + "type": "array", + "uniqueItems": true + }' + ), + array( + '[{}, [1], true, null, {}, 1]', + '{ + "type": "array", + "uniqueItems": true + }' ) ); } @@ -54,6 +75,41 @@ public function getValidTests() "type": "array", "uniqueItems": true }' + ), + array( + '[1, true]', + '{ + "type": "array", + "uniqueItems": true + }' + ), + array( + '[0, false]', + '{ + "type": "array", + "uniqueItems": true + }' + ), + array( + '[{"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : false}}}]', + '{ + "type": "array", + "uniqueItems": true + }' + ), + array( + '[["foo"], ["bar"]]', + '{ + "type": "array", + "uniqueItems": true + }' + ), + array( + '[{}, [1], true, null, 1]', + '{ + "type": "array", + "uniqueItems": true + }' ) ); } From c30751a5aad8ec4974e823316bd7bbef2f715327 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Tue, 7 May 2013 09:31:22 -0700 Subject: [PATCH 07/13] Fix issues with the use of disallow. Add draft v3 disallow unit tests. --- src/JsonSchema/Constraints/Undefined.php | 4 +- .../Tests/Constraints/DisallowTest.php | 85 ++++++++++++++++++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/JsonSchema/Constraints/Undefined.php b/src/JsonSchema/Constraints/Undefined.php index ea22d4fb..6e913aa5 100644 --- a/src/JsonSchema/Constraints/Undefined.php +++ b/src/JsonSchema/Constraints/Undefined.php @@ -111,7 +111,9 @@ protected function validateCommonProperties($value, $schema = null, $path = null if (isset($schema->disallow)) { $initErrors = $this->getErrors(); - $this->checkUndefined($value, $schema->disallow, $path); + $typeSchema = new \stdClass(); + $typeSchema->type = $schema->disallow; + $this->checkType($value, $typeSchema, $path); // if no new errors were raised it must be a disallowed value if (count($this->getErrors()) == count($initErrors)) { diff --git a/tests/JsonSchema/Tests/Constraints/DisallowTest.php b/tests/JsonSchema/Tests/Constraints/DisallowTest.php index 059d69ec..07446d9d 100644 --- a/tests/JsonSchema/Tests/Constraints/DisallowTest.php +++ b/tests/JsonSchema/Tests/Constraints/DisallowTest.php @@ -16,7 +16,7 @@ public function getInvalidTests() return array( array( '{ - "value":" The xpto is weird" + "value":"The xpto is weird" }', '{ "type":"object", @@ -41,6 +41,60 @@ public function getInvalidTests() } } }' + ), + array( + '{"value": 1}', + '{ + "type": "object", + "properties": { + "value": {"type": "any", "disallow": "integer"} + } + }' + ), + array( + '{"value": true}', + '{ + "type": "object", + "properties": { + "value": {"type": "any", "disallow": ["integer", "boolean"]} + } + }' + ), + array( + '{"value": "foo"}', + '{ + "type": "object", + "properties": { + "value": { + "type": "any", + "disallow": + ["string", { + "type": "object", + "properties": { + "foo": {"type": "string"} + } + }] + } + } + }' + ), + array( + '{"value": {"foo": "bar"}}', + '{ + "type": "object", + "properties": { + "value": { + "type": "any", + "disallow": + ["string", { + "type": "object", + "properties": { + "foo": {"type": "string"} + } + }] + } + } + }' ) ); } @@ -50,7 +104,7 @@ public function getValidTests() return array( array( '{ - "value":" The xpto is weird" + "value":"The xpto is weird" }', '{ "type":"object", @@ -75,6 +129,33 @@ public function getValidTests() } } }' + ), + array( + '{"value": {"foo": 1}}', + '{ + "type": "object", + "properties": { + "value": { + "type": "any", + "disallow": + ["string", { + "type": "object", + "properties": { + "foo": {"type": "string"} + } + }] + } + } + }' + ), + array( + '{"value": true}', + '{ + "type": "object", + "properties": { + "value": {"type": "any", "disallow": "string"} + } + }' ) ); } From c8f0f003df1051535d62b688c96c39114e85acf9 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Wed, 8 May 2013 15:01:57 -0700 Subject: [PATCH 08/13] Implement dependencies per draft specification. Refactor required. --- src/JsonSchema/Constraints/Constraint.php | 6 +- src/JsonSchema/Constraints/Object.php | 17 +- src/JsonSchema/Constraints/Undefined.php | 71 +++++++-- .../Tests/Constraints/DependenciesTest.php | 148 ++++++++++++++++++ .../Constraints/RequiredPropertyTest.php | 6 + 5 files changed, 217 insertions(+), 31 deletions(-) create mode 100644 tests/JsonSchema/Tests/Constraints/DependenciesTest.php diff --git a/src/JsonSchema/Constraints/Constraint.php b/src/JsonSchema/Constraints/Constraint.php index b946e800..b3cdcd88 100644 --- a/src/JsonSchema/Constraints/Constraint.php +++ b/src/JsonSchema/Constraints/Constraint.php @@ -151,13 +151,11 @@ protected function checkArray($value, $schema = null, $path = null, $i = null) * @param mixed $path * @param mixed $i * @param mixed $patternProperties - * @param mixed $requiredProp */ - protected function checkObject( - $value, $schema = null, $path = null, $i = null, $patternProperties = null, $requiredProp = null) + protected function checkObject($value, $schema = null, $path = null, $i = null, $patternProperties = null) { $validator = new Object($this->checkMode, $this->uriRetriever); - $validator->check($value, $schema, $path, $i, $patternProperties, $requiredProp); + $validator->check($value, $schema, $path, $i, $patternProperties); $this->addErrors($validator->getErrors()); } diff --git a/src/JsonSchema/Constraints/Object.php b/src/JsonSchema/Constraints/Object.php index fad49ec5..0d48b8b9 100644 --- a/src/JsonSchema/Constraints/Object.php +++ b/src/JsonSchema/Constraints/Object.php @@ -20,9 +20,7 @@ class Object extends Constraint /** * {@inheritDoc} */ - function check( - $element, $definition = null, - $path = null, $additionalProp = null, $patternProperties = null, $requiredProp = null) + function check($element, $definition = null, $path = null, $additionalProp = null, $patternProperties = null) { if ($element instanceof Undefined) { return; @@ -35,7 +33,7 @@ function check( if ($definition) { // validate the definition properties - $this->validateDefinition($element, $definition, $requiredProp, $path); + $this->validateDefinition($element, $definition, $path); } // additional the element properties @@ -108,24 +106,15 @@ public function validateElement($element, $matches, $objectDefinition = null, $p * * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition Object definition - * @param array $requiredProp Required properties * @param string $path Path? */ - public function validateDefinition($element, $objectDefinition = null, $requiredProp = null, $path = null) + public function validateDefinition($element, $objectDefinition = null, $path = null) { foreach ($objectDefinition as $i => $value) { $property = $this->getProperty($element, $i, new Undefined()); $definition = $this->getProperty($objectDefinition, $i); $this->checkUndefined($property, $definition, $path, $i); } - - if ($requiredProp) { - foreach ($requiredProp as $required) { - if (!$this->getProperty($element, $required)) { - $this->addError($path, "the property " . $required . " is required"); - } - } - } } /** diff --git a/src/JsonSchema/Constraints/Undefined.php b/src/JsonSchema/Constraints/Undefined.php index 6e913aa5..8281c948 100644 --- a/src/JsonSchema/Constraints/Undefined.php +++ b/src/JsonSchema/Constraints/Undefined.php @@ -40,10 +40,10 @@ public function check($value, $schema = null, $path = null, $i = null) /** * Validates the value against the types * - * @param $value - * @param null $schema - * @param null $path - * @param null $i + * @param mixed $value + * @param \stdClass $schema + * @param string $path + * @param string $i */ public function validateTypes($value, $schema = null, $path = null, $i = null) { @@ -59,8 +59,7 @@ public function validateTypes($value, $schema = null, $path = null, $i = null) isset($schema->properties) ? $schema->properties : null, $path, isset($schema->additionalProperties) ? $schema->additionalProperties : null, - isset($schema->patternProperties) ? $schema->patternProperties : null, - isset($schema->required) ? $schema->required : null + isset($schema->patternProperties) ? $schema->patternProperties : null ); } @@ -83,10 +82,10 @@ public function validateTypes($value, $schema = null, $path = null, $i = null) /** * Validates common properties * - * @param $value - * @param null $schema - * @param null $path - * @param null $i + * @param mixed $value + * @param \stdClass $schema + * @param string $path + * @param string $i */ protected function validateCommonProperties($value, $schema = null, $path = null, $i = null) { @@ -99,9 +98,21 @@ protected function validateCommonProperties($value, $schema = null, $path = null } // Verify required values - if (is_object($value) && $value instanceof Undefined) { - if (isset($schema->required) && $schema->required) { - $this->addError($path, "is missing and it is required"); + if (is_object($value)) { + if ($value instanceof Undefined) { + // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} + if (isset($schema->required) && $schema->required) { + $this->addError($path, "is missing and it is required"); + } + } else if (isset($schema->required)) { + // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] + foreach ($schema->required as $required) { + if (!property_exists($value, $required)) { + $this->addError($path, "the property " . $required . " is required"); + } + } + } else { + $this->checkType($value, $schema, $path); } } else { $this->checkType($value, $schema, $path); @@ -122,6 +133,40 @@ protected function validateCommonProperties($value, $schema = null, $path = null $this->errors = $initErrors; } } + + // Verify that dependencies are met + if (is_object($value) && isset($schema->dependencies)) { + $this->validateDependencies($value, $schema->dependencies, $path); + } + } + + /** + * @param \stdClass $value Element to validate + * @param mixed $dependencies Dependencies + * @param string $path Path? + */ + protected function validateDependencies($value, $dependencies, $path) + { + foreach ($dependencies as $key => $dependency) { + if (property_exists($value, $key)) { + if (is_string($dependency)) { + // Draft 3 string is allowed - e.g. "dependencies": "foo" + if (!property_exists($value, $dependency)) { + $this->addError($path, "$key depends on $dependency and $dependency is missing"); + } + } else if (is_array($dependency)) { + // Draft 4 doesn't allow string so must be an array - e.g. "dependencies": ["foo"] + foreach ($dependency as $d) { + if (!property_exists($value, $d)) { + $this->addError($path, "$key depends on $d and $d is missing"); + } + } + } else if (is_object($dependency)) { + // The dependency is a schema - e.g. "dependencies": {"properties": {"foo": {...}}} + $this->checkUndefined($value, $dependency, $path, ""); + } + } + } } protected function validateUri($schemaUri = null, $schema, $path = null, $i = null) diff --git a/tests/JsonSchema/Tests/Constraints/DependenciesTest.php b/tests/JsonSchema/Tests/Constraints/DependenciesTest.php new file mode 100644 index 00000000..daf9177b --- /dev/null +++ b/tests/JsonSchema/Tests/Constraints/DependenciesTest.php @@ -0,0 +1,148 @@ + Date: Wed, 8 May 2013 15:24:30 -0700 Subject: [PATCH 09/13] Fix Scrutinizer errors. --- src/JsonSchema/Constraints/Undefined.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/JsonSchema/Constraints/Undefined.php b/src/JsonSchema/Constraints/Undefined.php index 8281c948..c5396635 100644 --- a/src/JsonSchema/Constraints/Undefined.php +++ b/src/JsonSchema/Constraints/Undefined.php @@ -40,10 +40,10 @@ public function check($value, $schema = null, $path = null, $i = null) /** * Validates the value against the types * - * @param mixed $value - * @param \stdClass $schema - * @param string $path - * @param string $i + * @param $value + * @param null $schema + * @param null $path + * @param null $i */ public function validateTypes($value, $schema = null, $path = null, $i = null) { @@ -82,10 +82,10 @@ public function validateTypes($value, $schema = null, $path = null, $i = null) /** * Validates common properties * - * @param mixed $value - * @param \stdClass $schema - * @param string $path - * @param string $i + * @param $value + * @param null $schema + * @param null $path + * @param null $i */ protected function validateCommonProperties($value, $schema = null, $path = null, $i = null) { @@ -141,9 +141,11 @@ protected function validateCommonProperties($value, $schema = null, $path = null } /** - * @param \stdClass $value Element to validate - * @param mixed $dependencies Dependencies - * @param string $path Path? + * Validate dependencies + * + * @param $value + * @param null $dependencies + * @param null $path */ protected function validateDependencies($value, $dependencies, $path) { From bf926105065e35d0b442906f5430b45c684d7f9f Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Wed, 8 May 2013 15:46:12 -0700 Subject: [PATCH 10/13] Fix Scrutinizer errors. --- .../Constraints/ConstraintInterface.php | 6 ++--- src/JsonSchema/Constraints/Undefined.php | 24 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/JsonSchema/Constraints/ConstraintInterface.php b/src/JsonSchema/Constraints/ConstraintInterface.php index f30164fe..8b1eadb2 100644 --- a/src/JsonSchema/Constraints/ConstraintInterface.php +++ b/src/JsonSchema/Constraints/ConstraintInterface.php @@ -50,9 +50,9 @@ function isValid(); * * @abstract * @param mixed $value - * @param null $schema - * @param null $path - * @param null $i + * @param mixed $schema + * @param mixed $path + * @param mixed $i */ function check($value, $schema = null, $path = null, $i = null); } \ No newline at end of file diff --git a/src/JsonSchema/Constraints/Undefined.php b/src/JsonSchema/Constraints/Undefined.php index c5396635..fdd85a16 100644 --- a/src/JsonSchema/Constraints/Undefined.php +++ b/src/JsonSchema/Constraints/Undefined.php @@ -9,8 +9,6 @@ namespace JsonSchema\Constraints; -use JsonSchema\Validator; - /** * The Undefined Constraints * @@ -40,10 +38,10 @@ public function check($value, $schema = null, $path = null, $i = null) /** * Validates the value against the types * - * @param $value - * @param null $schema - * @param null $path - * @param null $i + * @param mixed $value + * @param mixed $schema + * @param string $path + * @param string $i */ public function validateTypes($value, $schema = null, $path = null, $i = null) { @@ -82,10 +80,10 @@ public function validateTypes($value, $schema = null, $path = null, $i = null) /** * Validates common properties * - * @param $value - * @param null $schema - * @param null $path - * @param null $i + * @param mixed $value + * @param mixed $schema + * @param string $path + * @param string $i */ protected function validateCommonProperties($value, $schema = null, $path = null, $i = null) { @@ -143,9 +141,9 @@ protected function validateCommonProperties($value, $schema = null, $path = null /** * Validate dependencies * - * @param $value - * @param null $dependencies - * @param null $path + * @param mixed $value + * @param mixed $dependencies + * @param string $path */ protected function validateDependencies($value, $dependencies, $path) { From 62aef117bb60e529e6801fa6c36dd965d7a0472a Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Thu, 9 May 2013 11:55:06 -0700 Subject: [PATCH 11/13] Fix extends so that it accepts a schema or an array of schemas. --- src/JsonSchema/Constraints/Undefined.php | 19 ++- .../Tests/Constraints/ExtendsTest.php | 108 +++++++++++++----- 2 files changed, 91 insertions(+), 36 deletions(-) diff --git a/src/JsonSchema/Constraints/Undefined.php b/src/JsonSchema/Constraints/Undefined.php index fdd85a16..5316000d 100644 --- a/src/JsonSchema/Constraints/Undefined.php +++ b/src/JsonSchema/Constraints/Undefined.php @@ -90,9 +90,16 @@ protected function validateCommonProperties($value, $schema = null, $path = null // if it extends another schema, it must pass that schema as well if (isset($schema->extends)) { if (is_string($schema->extends)) { - $schema->extends = $this->validateUri($schema->extends, $schema, $path, $i); + $schema->extends = $this->validateUri($schema, $schema->extends); + } + $increment = is_null($i) ? "" : $i; + if (is_array($schema->extends)) { + foreach ($schema->extends as $extends) { + $this->checkUndefined($value, $extends, $path, $increment); + } + } else { + $this->checkUndefined($value, $schema->extends, $path, $increment); } - $this->checkUndefined($value, $schema->extends, $path, $i); } // Verify required values @@ -150,26 +157,26 @@ protected function validateDependencies($value, $dependencies, $path) foreach ($dependencies as $key => $dependency) { if (property_exists($value, $key)) { if (is_string($dependency)) { - // Draft 3 string is allowed - e.g. "dependencies": "foo" + // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} if (!property_exists($value, $dependency)) { $this->addError($path, "$key depends on $dependency and $dependency is missing"); } } else if (is_array($dependency)) { - // Draft 4 doesn't allow string so must be an array - e.g. "dependencies": ["foo"] + // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} foreach ($dependency as $d) { if (!property_exists($value, $d)) { $this->addError($path, "$key depends on $d and $d is missing"); } } } else if (is_object($dependency)) { - // The dependency is a schema - e.g. "dependencies": {"properties": {"foo": {...}}} + // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}} $this->checkUndefined($value, $dependency, $path, ""); } } } } - protected function validateUri($schemaUri = null, $schema, $path = null, $i = null) + protected function validateUri($schema, $schemaUri = null) { $resolver = new \JsonSchema\Uri\UriResolver(); $retriever = $this->getUriRetriever(); diff --git a/tests/JsonSchema/Tests/Constraints/ExtendsTest.php b/tests/JsonSchema/Tests/Constraints/ExtendsTest.php index ac15df3c..289484f3 100644 --- a/tests/JsonSchema/Tests/Constraints/ExtendsTest.php +++ b/tests/JsonSchema/Tests/Constraints/ExtendsTest.php @@ -20,22 +20,16 @@ public function getInvalidTests() "age":50 }', '{ - "id": "person", - "type": "object", "properties": { - "name": { - "type": "string" - }, - "age" : { + "name": {"type": "string"}, + "age": { "type": "integer", - "maximum":120 + "maximum": 120 } }, "extends": { - "id": "oldPerson", - "type": "object", "properties": { - "age" : {"minimum":70} + "age": {"minimum": 70} } } }' @@ -46,25 +40,52 @@ public function getInvalidTests() "age":180 }', '{ - "id": "person", - "type": "object", "properties": { - "name": { - "type": "string" - }, - "age" : { + "name": {"type": "string"}, + "age": { "type": "integer", - "maximum":120 + "maximum": 120 + } + }, + "extends": { + "properties": { + "age": {"minimum":70} } + } + }' + ), + array( + '{"foo": 2, "bar": "baz"}', + '{ + "properties": { + "bar": {"type": "integer", "required": true} }, "extends": { - "id": "oldPerson", - "type": "object", "properties": { - "age" : {"minimum":70} + "foo": {"type": "string", "required": true} } } }' + ), + array( + '{"bar": 2}', + '{ + "properties": { + "bar": {"type": "integer", "required": true} + }, + "extends" : [ + { + "properties": { + "foo": {"type": "string", "required": true} + } + }, + { + "properties": { + "baz": {"type": "null", "required": true} + } + } + ] + }' ) ); } @@ -78,25 +99,52 @@ public function getValidTests() "age":80 }', '{ - "id": "person", - "type": "object", "properties": { - "name": { - "type": "string" - }, - "age" : { + "name": {"type": "string"}, + "age": { "type": "integer", - "maximum":120 + "maximum": 120 } }, "extends": { - "id": "oldPerson", - "type": "object", "properties": { - "age" : {"minimum":70} + "age": {"minimum": 70} } } }' + ), + array( + '{"foo": "baz", "bar": 2}', + '{ + "properties": { + "bar": {"type": "integer", "required": true} + }, + "extends": { + "properties": { + "foo": {"type": "string", "required": true} + } + } + }' + ), + array( + '{"foo": "ick", "bar": 2, "baz": null}', + '{ + "properties": { + "bar": {"type": "integer", "required": true} + }, + "extends" : [ + { + "properties": { + "foo": {"type": "string", "required": true} + } + }, + { + "properties": { + "baz": {"type": "null", "required": true} + } + } + ] + }' ) ); } From 38ceb740188c378f78ed91dc95496446a751b492 Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Thu, 9 May 2013 17:34:34 -0700 Subject: [PATCH 12/13] Fix regex format validation and fix the regex format unit tests. --- src/JsonSchema/Constraints/Format.php | 12 +++++------- tests/JsonSchema/Tests/Constraints/FormatTest.php | 5 ++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/JsonSchema/Constraints/Format.php b/src/JsonSchema/Constraints/Format.php index 7468f65c..9262d6bd 100644 --- a/src/JsonSchema/Constraints/Format.php +++ b/src/JsonSchema/Constraints/Format.php @@ -56,7 +56,9 @@ public function check($element, $schema = null, $path = null, $i = null) break; case 'regex': - $this->validateRegex($path, $element, $schema->pattern); + if (!$this->validateRegex($element)) { + $this->addError($path, 'Invalid regex format ' . $element); + } break; case 'color': @@ -124,13 +126,9 @@ protected function validateDateTime($datetime, $format) return $datetime === $dt->format($format); } - protected function validateRegex($path, $element, $regex) + protected function validateRegex($regex) { - if (false === @preg_match('/' . $regex . '/', '')) { - $this->addError($path, 'Invalid regex format ' . $regex); - } else if (!preg_match('/' . $regex . '/', $element)) { - $this->addError($path, sprintf('%s does not match regex format %s', json_encode($element), $regex)); - } + return false !== @preg_match('/' . $regex . '/', ''); } protected function validateColor($color) diff --git a/tests/JsonSchema/Tests/Constraints/FormatTest.php b/tests/JsonSchema/Tests/Constraints/FormatTest.php index 2f2e1bec..1f3c2bd6 100644 --- a/tests/JsonSchema/Tests/Constraints/FormatTest.php +++ b/tests/JsonSchema/Tests/Constraints/FormatTest.php @@ -27,12 +27,11 @@ public function testRegex() $validator = new Format(); $schema = new \stdClass; $schema->format = 'regex'; - $schema->pattern = '\d+'; - $validator->check('10', $schema); + $validator->check('\d+', $schema); $this->assertEmpty($validator->getErrors()); - $validator->check('ten', $schema); + $validator->check('^(abc]', $schema); $this->assertCount(1, $validator->getErrors()); } From 65bfb0a3c10dbf4212052296c472078f54f3912b Mon Sep 17 00:00:00 2001 From: Michael Chiocca Date: Fri, 10 May 2013 10:16:14 -0700 Subject: [PATCH 13/13] Fix divisibleBy for small numbers per draft unit tests. --- src/JsonSchema/Constraints/Number.php | 20 +++++++++++++++++-- .../Tests/Constraints/DivisibleByTest.php | 16 +++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/JsonSchema/Constraints/Number.php b/src/JsonSchema/Constraints/Number.php index 4750aee3..8bfd594c 100644 --- a/src/JsonSchema/Constraints/Number.php +++ b/src/JsonSchema/Constraints/Number.php @@ -53,10 +53,26 @@ public function check($element, $schema = null, $path = null, $i = null) } // Verify divisibleBy - if (isset($schema->divisibleBy) && fmod($element, $schema->divisibleBy) != 0) { + if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { $this->addError($path, "is not divisible by " . $schema->divisibleBy); } $this->checkFormat($element, $schema, $path, $i); } -} \ No newline at end of file + + private function fmod($number1, $number2) + { + $modulus = fmod($number1, $number2); + $precision = abs(0.0000000001); + $diff = (float)($modulus - $number2); + + if (-$precision < $diff && $diff < $precision) { + return 0.0; + } + + $decimals1 = mb_strpos($number1, ".") ? mb_strlen($number1) - mb_strpos($number1, ".") - 1 : 0; + $decimals2 = mb_strpos($number2, ".") ? mb_strlen($number2) - mb_strpos($number2, ".") - 1 : 0; + + return (float)round($modulus, max($decimals1, $decimals2)); + } +} diff --git a/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php b/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php index ba77c6aa..8a010965 100644 --- a/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php +++ b/tests/JsonSchema/Tests/Constraints/DivisibleByTest.php @@ -73,6 +73,22 @@ public function getValidTests() "value": {"type": "number", "divisibleBy": 1.5} } }' + ), + array( + '{"value": 0.0075}', + '{ + "properties": { + "value": {"type": "number", "divisibleBy": 0.0001} + } + }' + ), + array( + '{"value": 1}', + '{ + "properties": { + "value": {"type": "number", "divisibleBy": 0.02} + } + }' ) ); }