Skip to content

Fix issues #32 and #33. Draft v3 compliance excluding $ref, $schema, and id. #41

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

Merged
merged 13 commits into from
May 28, 2013
Merged
Show file tree
Hide file tree
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
36 changes: 21 additions & 15 deletions src/JsonSchema/Constraints/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 var_export($e, true); }, $value);
}
if (count(array_unique($unique)) != count($value)) {
$this->addError($path, "There are no duplicates allowed in the array");
}
}

// Verify items
Expand All @@ -54,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)) {
Expand All @@ -71,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;
}
}
Expand All @@ -84,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);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/JsonSchema/Constraints/ConstraintInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
8 changes: 6 additions & 2 deletions src/JsonSchema/Constraints/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
13 changes: 7 additions & 6 deletions src/JsonSchema/Constraints/Format.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -55,8 +56,8 @@ 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");
if (!$this->validateRegex($element)) {
$this->addError($path, 'Invalid regex format ' . $element);
}
break;

Expand Down Expand Up @@ -125,9 +126,9 @@ protected function validateDateTime($datetime, $format)
return $datetime === $dt->format($format);
}

protected function validateRegex($string, $regex)
protected function validateRegex($regex)
{
return preg_match('/' . $regex . '/', $string);
return false !== @preg_match('/' . $regex . '/', '');
}

protected function validateColor($color)
Expand Down
44 changes: 40 additions & 4 deletions src/JsonSchema/Constraints/Number.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,56 @@ 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);
}

// Verify divisibleBy
if (isset($schema->divisibleBy) && $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);
}
}

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));
}
}
4 changes: 2 additions & 2 deletions src/JsonSchema/Constraints/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
89 changes: 72 additions & 17 deletions src/JsonSchema/Constraints/Undefined.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

namespace JsonSchema\Constraints;

use JsonSchema\Validator;

/**
* The Undefined Constraints
*
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -82,25 +80,44 @@ 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)
{
// 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
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);
Expand All @@ -110,7 +127,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)) {
Expand All @@ -119,9 +138,45 @@ 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);
}
}

/**
* Validate dependencies
*
* @param mixed $value
* @param mixed $dependencies
* @param string $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": {"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 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)) {
// 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();
Expand Down
Loading