Skip to content

Add if then else schema validation #715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
8 changes: 8 additions & 0 deletions src/JsonSchema/ConstraintError.php
Original file line number Diff line number Diff line change
@@ -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',
5 changes: 5 additions & 0 deletions src/JsonSchema/Constraints/ObjectConstraint.php
Original file line number Diff line number Diff line change
@@ -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) {
31 changes: 30 additions & 1 deletion src/JsonSchema/Constraints/UndefinedConstraint.php
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Uri\UriResolver;
use JsonSchema\Validator;

/**
* The UndefinedConstraint Constraints
@@ -46,7 +47,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 +373,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);
}
}
}
}

/**
231 changes: 231 additions & 0 deletions tests/Constraints/IfThenElseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
<?php

/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace JsonSchema\Tests\Constraints;

class IfThenElseTest extends BaseTestCase
{
protected $validateSchema = true;

public function getInvalidTests()
{
return array(
// If "foo" === "bar", then "bar" must be defined, else Validation Failed.
// But "foo" === "bar" and "bar" is not defined.
array(
'{
"foo":"bar"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": {
"properties": {"foo": {"enum": ["bar"]}},
"required": ["foo"]
},
"then": {"required": ["bar"]},
"else": false
}'
),
// If "foo" === "bar", then "bar" must be defined, else Validation Failed.
// But "foo" !== "bar".
array(
'{
"foo":"baz"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": {
"properties": {"foo": {"enum": ["bar"]}},
"required": ["foo"]
},
"then": {"required": ["bar"]},
"else": false
}'
),
// If "foo" === "bar", then "bar" must === "baz", else Validation Failed.
// But "foo" === "bar" and "bar" !== "baz".
array(
'{
"foo":"bar",
"bar":"potato"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": {
"properties": {"foo": {"enum": ["bar"]}},
"required": ["foo"]
},
"then": {
"properties": {"bar": {"enum": ["baz"]}},
"required": ["bar"]
},
"else": false
}'
),
// Always go to "else".
// But schema is invalid.
array(
'{
"foo":"bar"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": false,
"then": true,
"else": {
"properties": {"bar": {"enum": ["baz"]}},
"required": ["bar"]
}
}'
),
// Always go to "then".
// But schema is invalid.
array(
'{
"foo":"bar"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": true,
"then": {
"properties": {"bar": {"enum": ["baz"]}},
"required": ["bar"]
},
"else": true
}'
)
);
}

public function getValidTests()
{
return array(
// Always validate.
array(
'{
"foo":"bar"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": true,
"then": true,
"else": false
}'
),
// Always validate schema in then.
array(
'{
"foo":"bar"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": true,
"then": {
"properties": {"foo": {"enum": ["bar"]}},
"required": ["foo"]
},
"else": false
}'
),
// Always validate schema in else.
array(
'{
"foo":"bar"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": false,
"then": false,
"else": {
"properties": {"foo": {"enum": ["bar"]}},
"required": ["foo"]
}
}'
),
// "If" is evaluated to true, so "then" is to validate.
array(
'{
"foo":"bar",
"bar":"baz"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": {
"properties": {"foo": {"enum": ["bar"]}},
"required": ["foo"]
},
"then": {
"properties": {"bar": {"enum": ["baz"]}},
"required": ["bar"]
},
"else": false
}'
),
// "If" is evaluated to false, so "else" is to validate.
array(
'{
"foo":"bar",
"bar":"baz"
}',
'{
"type": "object",
"properties": {
"foo": {"type": "string"},
"bar": {"type": "string"}
},
"if": {
"properties": {"foo": {"enum": ["potato"]}},
"required": ["foo"]
},
"then": false,
"else": {
"properties": {"bar": {"enum": ["baz"]}},
"required": ["bar"]
}
}'
),
);
}
}
Loading