From d382e5217cdfeffdbf59136ed0f554a98abb17e3 Mon Sep 17 00:00:00 2001 From: Nicolas Giraud Date: Mon, 19 Feb 2024 15:35:44 +0100 Subject: [PATCH 1/3] Update the JSON Schema Test Suite to version 2.0.0 --- composer.json | 7 +-- .../Constraints/ObjectConstraint.php | 5 ++ tests/Constraints/MinMaxPropertiesTest.php | 51 +++++++++++++++---- tests/Drafts/Draft4Test.php | 13 +++-- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index c6b6936c..df1b806c 100644 --- a/composer.json +++ b/composer.json @@ -28,12 +28,13 @@ ], "require": { "php": ">=5.3.3", + "ext-json": "*", "marc-mabe/php-enum":"^2.0 || ^3.0 || ^4.0", "icecave/parity": "1.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20 || ~2.19.0", - "json-schema/json-schema-test-suite": "1.2.0", + "json-schema/json-schema-test-suite": "2.0.0", "phpunit/phpunit": "^4.8.35" }, "extra": { @@ -56,11 +57,11 @@ "type": "package", "package": { "name": "json-schema/json-schema-test-suite", - "version": "1.2.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/json-schema/JSON-Schema-Test-Suite", - "reference": "1.2.0" + "reference": "2.0.0" } } } diff --git a/src/JsonSchema/Constraints/ObjectConstraint.php b/src/JsonSchema/Constraints/ObjectConstraint.php index b4f85650..bd77c7ac 100644 --- a/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/src/JsonSchema/Constraints/ObjectConstraint.php @@ -173,6 +173,11 @@ protected function &getProperty(&$element, $property, $fallback = null) */ protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) { + // minProperties and maxProperties constraints only applies on objects elements. + if (!is_object($element)) { + return; + } + // Verify minimum number of properties if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { diff --git a/tests/Constraints/MinMaxPropertiesTest.php b/tests/Constraints/MinMaxPropertiesTest.php index 90774b91..8ce2647c 100644 --- a/tests/Constraints/MinMaxPropertiesTest.php +++ b/tests/Constraints/MinMaxPropertiesTest.php @@ -63,6 +63,39 @@ public function getValidTests() } }' ), + // Ignore array. + array( + '{ + "value": [] + }', + '{ + "properties": { + "value": {"minProperties": 1,"maxProperties": 2} + } + }' + ), + // Ignore string. + array( + '{ + "value": "foo" + }', + '{ + "properties": { + "value": {"minProperties": 1,"maxProperties": 2} + } + }' + ), + // Ignore anything that is non-object. + array( + '{ + "value": 42 + }', + '{ + "properties": { + "value": {"minProperties": 1,"maxProperties": 2} + } + }' + ), ); } @@ -123,16 +156,14 @@ public function getInvalidTests() } }' ), - array( - '{ - "value": [] - }', - '{ - "properties": { - "value": {"minProperties": 1,"maxProperties": 2} - } - }' - ), ); } + + /** + * {@inheritdoc} + */ + public function getInvalidForAssocTests() + { + return array(); + } } diff --git a/tests/Drafts/Draft4Test.php b/tests/Drafts/Draft4Test.php index 54eee4c4..73d1d5a9 100644 --- a/tests/Drafts/Draft4Test.php +++ b/tests/Drafts/Draft4Test.php @@ -33,7 +33,10 @@ public function getInvalidForAssocTests() $tests = parent::getInvalidForAssocTests(); unset( $tests['type.json / object type matches objects / an array is not an object'], - $tests['type.json / array type matches arrays / an object is not an array'] + $tests['type.json / array type matches arrays / an object is not an array'], + // Arrays must be ignored and in assoc case, these data are arrays and not objects. + $tests['maxProperties.json / maxProperties validation / too long is invalid'], + $tests['minProperties.json / minProperties validation / too short is invalid'] ); return $tests; @@ -44,7 +47,9 @@ public function getValidForAssocTests() $tests = parent::getValidForAssocTests(); unset( $tests['type.json / object type matches objects / an array is not an object'], - $tests['type.json / array type matches arrays / an object is not an array'] + $tests['type.json / array type matches arrays / an object is not an array'], + // Arrays must be ignored and in assoc case, these data are arrays and not objects. + $tests['required.json / required validation / ignores arrays'] ); return $tests; @@ -58,10 +63,12 @@ protected function getSkippedTests() return array( // Optional 'bignum.json', + 'ecmascript-regex.json', 'format.json', 'zeroTerminatedFloats.json', // Required - 'not.json' // only one test case failing + 'not.json', // only one test case failing + 'ref.json', // external references can not be found (localhost:1234) ); } } From 7fc2e9354e86c135d33d518058fac42335d4b352 Mon Sep 17 00:00:00 2001 From: Nicolas Giraud Date: Mon, 19 Feb 2024 15:45:00 +0100 Subject: [PATCH 2/3] Add If-Then-Else schema validation. --- src/JsonSchema/ConstraintError.php | 8 + .../Constraints/UndefinedConstraint.php | 30 ++- tests/Constraints/IfThenElseTest.php | 231 ++++++++++++++++++ 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 tests/Constraints/IfThenElseTest.php diff --git a/src/JsonSchema/ConstraintError.php b/src/JsonSchema/ConstraintError.php index fd5ba8e6..de49cdd0 100644 --- a/src/JsonSchema/ConstraintError.php +++ b/src/JsonSchema/ConstraintError.php @@ -9,7 +9,11 @@ class ConstraintError extends Enum const ADDITIONAL_ITEMS = 'additionalItems'; const ADDITIONAL_PROPERTIES = 'additionalProp'; const ALL_OF = 'allOf'; + const ALWAYS_FAILS = 'alwaysFails'; const ANY_OF = 'anyOf'; + const CONDITIONAL_IF = 'if'; + const CONDITIONAL_THEN = 'then'; + const CONDITIONAL_ELSE = 'else'; const DEPENDENCIES = 'dependencies'; const DISALLOW = 'disallow'; const DIVISIBLE_BY = 'divisibleBy'; @@ -59,12 +63,16 @@ public function getMessage() self::ADDITIONAL_ITEMS => 'The item %s[%s] is not defined and the definition does not allow additional items', self::ADDITIONAL_PROPERTIES => 'The property %s is not defined and the definition does not allow additional properties', self::ALL_OF => 'Failed to match all schemas', + self::ALWAYS_FAILS => 'Schema always fails validation', self::ANY_OF => 'Failed to match at least one schema', self::DEPENDENCIES => '%s depends on %s, which is missing', self::DISALLOW => 'Disallowed value was matched', self::DIVISIBLE_BY => 'Is not divisible by %d', self::ENUM => 'Does not have a value in the enumeration %s', self::CONSTANT => 'Does not have a value equal to %s', + self::CONDITIONAL_IF => 'The keyword "if" must be a boolean or an object', + self::CONDITIONAL_THEN => 'The keyword "then" must be a boolean or an object', + self::CONDITIONAL_ELSE => 'The keyword "else" must be a boolean or an object', self::EXCLUSIVE_MINIMUM => 'Must have a minimum value greater than %d', self::EXCLUSIVE_MAXIMUM => 'Must have a maximum value less than %d', self::FORMAT_COLOR => 'Invalid color', diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index aa5e664f..0e1903d8 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -46,7 +46,7 @@ public function check(&$value, $schema = null, JsonPointer $path = null, $i = nu // check special properties $this->validateCommonProperties($value, $schema, $path, $i); - // check allOf, anyOf, and oneOf properties + // check allOf, anyOf, oneOf, if, then, and else properties $this->validateOfProperties($value, $schema, $path, ''); // check known types @@ -372,6 +372,34 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $this->errors = $startErrors; } } + + if (isset($schema->if)) { + if (!is_bool($schema->if) && !is_object($schema->if)) { + $this->addError(ConstraintError::CONDITIONAL_IF(), $path); + } + $validator = new Validator(); + if ($schema->if !== false && Validator::ERROR_NONE === $validator->validate($value, $schema->if)) { + if (isset($schema->then)) { + if (!is_bool($schema->then) && !is_object($schema->then)) { + $this->addError(ConstraintError::CONDITIONAL_THEN(), $path); + } + if ($schema->then === false) { + $this->addError(ConstraintError::ALWAYS_FAILS(), $path); + } else { + $this->check($value, $schema->then); + } + } + } elseif (isset($schema->else)) { + if (!is_bool($schema->else) && !is_object($schema->else)) { + $this->addError(ConstraintError::CONDITIONAL_ELSE(), $path); + } + if ($schema->else === false) { + $this->addError(ConstraintError::ALWAYS_FAILS(), $path); + } else { + $this->check($value, $schema->else); + } + } + } } /** diff --git a/tests/Constraints/IfThenElseTest.php b/tests/Constraints/IfThenElseTest.php new file mode 100644 index 00000000..afb93ed1 --- /dev/null +++ b/tests/Constraints/IfThenElseTest.php @@ -0,0 +1,231 @@ + Date: Mon, 19 Feb 2024 16:03:24 +0100 Subject: [PATCH 3/3] Fix missing include --- src/JsonSchema/Constraints/UndefinedConstraint.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 0e1903d8..bdb76a76 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -14,6 +14,7 @@ use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\ValidationException; use JsonSchema\Uri\UriResolver; +use JsonSchema\Validator; /** * The UndefinedConstraint Constraints