diff --git a/.travis.yml b/.travis.yml index 9e7174fa..82f8e589 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,4 +28,4 @@ install: - travis_retry composer install --no-interaction --prefer-dist script: - - if [[ "$WITH_COVERAGE" == "true" ]]; then composer coverage; else composer test; fi + - if [[ "$WITH_COVERAGE" == "true" ]]; then ./vendor/bin/phpunit --coverage-text; else composer test; fi diff --git a/src/JsonSchema/Constraints/BaseConstraint.php b/src/JsonSchema/Constraints/BaseConstraint.php new file mode 100644 index 00000000..9d2ade65 --- /dev/null +++ b/src/JsonSchema/Constraints/BaseConstraint.php @@ -0,0 +1,92 @@ +factory = $factory ? : new Factory(); + } + + /** + * {@inheritDoc} + */ + public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) + { + $error = array( + 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), + 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), + 'message' => $message, + 'constraint' => $constraint, + ); + + if (is_array($more) && count($more) > 0) + { + $error += $more; + } + + $this->errors[] = $error; + } + + /** + * {@inheritDoc} + */ + public function addErrors(array $errors) + { + if ($errors) { + $this->errors = array_merge($this->errors, $errors); + } + } + + /** + * {@inheritDoc} + */ + public function getErrors() + { + return $this->errors; + } + + /** + * {@inheritDoc} + */ + public function isValid() + { + return !$this->getErrors(); + } + + /** + * Clears any reported errors. Should be used between + * multiple validation checks. + */ + public function reset() + { + $this->errors = array(); + } +} diff --git a/src/JsonSchema/Constraints/CollectionConstraint.php b/src/JsonSchema/Constraints/CollectionConstraint.php index b71ec423..e2a704e9 100644 --- a/src/JsonSchema/Constraints/CollectionConstraint.php +++ b/src/JsonSchema/Constraints/CollectionConstraint.php @@ -23,20 +23,7 @@ class CollectionConstraint extends Constraint /** * {@inheritDoc} */ - public function check($value, $schema = null, JsonPointer $path = null, $i = null) - { - $this->_check($value, $schema, $path, $i); - } - - /** - * {@inheritDoc} - */ - public function coerce(&$value, $schema = null, JsonPointer $path = null, $i = null) - { - $this->_check($value, $schema, $path, $i, true); - } - - protected function _check(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + public function check(&$value, $schema = null, JsonPointer $path = null, $i = null) { // Verify minItems if (isset($schema->minItems) && count($value) < $schema->minItems) { @@ -61,7 +48,7 @@ protected function _check(&$value, $schema = null, JsonPointer $path = null, $i // Verify items if (isset($schema->items)) { - $this->validateItems($value, $schema, $path, $i, $coerce); + $this->validateItems($value, $schema, $path, $i); } } @@ -72,9 +59,8 @@ protected function _check(&$value, $schema = null, JsonPointer $path = null, $i * @param \stdClass $schema * @param JsonPointer|null $path * @param string $i - * @param boolean $coerce */ - protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null) { if (is_object($schema->items)) { // just one type definition for the whole array @@ -90,26 +76,24 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu // performance optimization $type = $schema->items->type; $typeValidator = $this->factory->createInstanceFor('type'); - $validator = $this->factory->createInstanceFor($type === 'integer' ? 'number' : $type); + $validator = $this->factory->createInstanceFor($type === 'integer' ? 'number' : $type); - foreach ($value as $k => $v) { - $k_path = $this->incrementPath($path, $k); - if($coerce) { - $typeValidator->coerce($v, $schema->items, $k_path, $i); - } else { - $typeValidator->check($v, $schema->items, $k_path, $i); - } + foreach ($value as $k => &$v) { + $k_path = $this->incrementPath($path, $k); + $typeValidator->check($v, $schema->items, $k_path, $i); - $validator->check($v, $schema->items, $k_path, $i); + $validator->check($v, $schema->items, $k_path, $i); } + unset($v); // remove dangling reference to prevent any future bugs + // caused by accidentally using $v elsewhere $this->addErrors($typeValidator->getErrors()); $this->addErrors($validator->getErrors()); } else { - foreach ($value as $k => $v) { + foreach ($value as $k => &$v) { $initErrors = $this->getErrors(); // First check if its defined in "items" - $this->checkUndefined($v, $schema->items, $path, $k, $coerce); + $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)) { @@ -124,32 +108,37 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu $this->errors = $initErrors; } } + unset($v); // remove dangling reference to prevent any future bugs + // caused by accidentally using $v elsewhere } } else { // Defined item type definitions - foreach ($value as $k => $v) { + foreach ($value as $k => &$v) { if (array_key_exists($k, $schema->items)) { - $this->checkUndefined($v, $schema->items[$k], $path, $k, $coerce); + $this->checkUndefined($v, $schema->items[$k], $path, $k); } else { // Additional items if (property_exists($schema, 'additionalItems')) { if ($schema->additionalItems !== false) { - $this->checkUndefined($v, $schema->additionalItems, $path, $k, $coerce); + $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', 'additionalItems', array('additionalItems' => $schema->additionalItems,)); } } else { // Should be valid against an empty schema - $this->checkUndefined($v, new \stdClass(), $path, $k, $coerce); + $this->checkUndefined($v, new \stdClass(), $path, $k); } } } + unset($v); // remove dangling reference to prevent any future bugs + // caused by accidentally using $v elsewhere // Treat when we have more schema definitions than values, not for empty arrays if (count($value) > 0) { for ($k = count($value); $k < count($schema->items); $k++) { - $this->checkUndefined($this->factory->createInstanceFor('undefined'), $schema->items[$k], $path, $k, $coerce); + $undefinedInstance = $this->factory->createInstanceFor('undefined'); + $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k); } } } diff --git a/src/JsonSchema/Constraints/Constraint.php b/src/JsonSchema/Constraints/Constraint.php index 5d27e431..c5170eea 100644 --- a/src/JsonSchema/Constraints/Constraint.php +++ b/src/JsonSchema/Constraints/Constraint.php @@ -20,81 +20,15 @@ * @author Robert Schönthal * @author Bruno Prieto Reis */ -abstract class Constraint implements ConstraintInterface +abstract class Constraint extends BaseConstraint implements ConstraintInterface { - protected $errors = array(); protected $inlineSchemaProperty = '$schema'; - const CHECK_MODE_NORMAL = 0x00000001; - const CHECK_MODE_TYPE_CAST = 0x00000002; - - /** - * @var Factory - */ - protected $factory; - - /** - * @param Factory $factory - */ - public function __construct(Factory $factory = null) - { - $this->factory = $factory ? : new Factory(); - } - - /** - * {@inheritDoc} - */ - public function addError(JsonPointer $path = null, $message, $constraint='', array $more=null) - { - $error = array( - 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), - 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), - 'message' => $message, - 'constraint' => $constraint, - ); - - if (is_array($more) && count($more) > 0) - { - $error += $more; - } - - $this->errors[] = $error; - } - - /** - * {@inheritDoc} - */ - public function addErrors(array $errors) - { - if ($errors) { - $this->errors = array_merge($this->errors, $errors); - } - } - - /** - * {@inheritDoc} - */ - public function getErrors() - { - return $this->errors; - } - - /** - * {@inheritDoc} - */ - public function isValid() - { - return !$this->getErrors(); - } - - /** - * Clears any reported errors. Should be used between - * multiple validation checks. - */ - public function reset() - { - $this->errors = array(); - } + const CHECK_MODE_NONE = 0x00000000; + const CHECK_MODE_NORMAL = 0x00000001; + const CHECK_MODE_TYPE_CAST = 0x00000002; + const CHECK_MODE_COERCE_TYPES = 0x00000004; + const CHECK_MODE_APPLY_DEFAULTS = 0x00000008; /** * Bubble down the path @@ -123,16 +57,11 @@ protected function incrementPath(JsonPointer $path = null, $i) * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i - * @param boolean $coerce */ - protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('collection'); - if($coerce) { - $validator->coerce($value, $schema, $path, $i); - } else { - $validator->check($value, $schema, $path, $i); - } + $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } @@ -145,16 +74,11 @@ protected function checkArray(&$value, $schema = null, JsonPointer $path = null, * @param JsonPointer|null $path * @param mixed $i * @param mixed $patternProperties - * @param boolean $coerce */ - protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $i = null, $patternProperties = null, $coerce = false) + protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $i = null, $patternProperties = null) { $validator = $this->factory->createInstanceFor('object'); - if($coerce){ - $validator->coerce($value, $schema, $path, $i, $patternProperties); - } else { - $validator->check($value, $schema, $path, $i, $patternProperties); - } + $validator->check($value, $schema, $path, $i, $patternProperties); $this->addErrors($validator->getErrors()); } @@ -166,16 +90,11 @@ protected function checkObject(&$value, $schema = null, JsonPointer $path = null * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i - * @param boolean $coerce */ - protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('type'); - if($coerce) { - $validator->coerce($value, $schema, $path, $i); - } else { - $validator->check($value, $schema, $path, $i); - } + $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } @@ -187,17 +106,12 @@ protected function checkType(&$value, $schema = null, JsonPointer $path = null, * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i - * @param boolean $coerce */ - protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('undefined'); - if($coerce){ - $validator->coerce($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i); - } else { - $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i); - } + $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i); $this->addErrors($validator->getErrors()); } diff --git a/src/JsonSchema/Constraints/ConstraintInterface.php b/src/JsonSchema/Constraints/ConstraintInterface.php index ca123639..0c2922a3 100644 --- a/src/JsonSchema/Constraints/ConstraintInterface.php +++ b/src/JsonSchema/Constraints/ConstraintInterface.php @@ -40,7 +40,7 @@ public function addErrors(array $errors); * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' * @param array $more more array elements to add to the error */ - public function addError(JsonPointer $path = null, $message, $constraint='', array $more=null); + public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null); /** * checks if the validator has not raised errors @@ -59,5 +59,5 @@ public function isValid(); * @param mixed $i * @throws \JsonSchema\Exception\ExceptionInterface */ - public function check($value, $schema = null, JsonPointer $path = null, $i = null); -} \ No newline at end of file + public function check(&$value, $schema = null, JsonPointer $path = null, $i = null); +} diff --git a/src/JsonSchema/Constraints/EnumConstraint.php b/src/JsonSchema/Constraints/EnumConstraint.php index 376d3ee9..18c3d707 100644 --- a/src/JsonSchema/Constraints/EnumConstraint.php +++ b/src/JsonSchema/Constraints/EnumConstraint.php @@ -21,7 +21,7 @@ class EnumConstraint extends Constraint /** * {@inheritDoc} */ - public function check($element, $schema = null, JsonPointer $path = null, $i = null) + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Only validate enum if the attribute exists if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { @@ -31,7 +31,7 @@ public function check($element, $schema = null, JsonPointer $path = null, $i = n foreach ($schema->enum as $enum) { $enumType = gettype($enum); - if (($this->factory->getCheckMode() & self::CHECK_MODE_TYPE_CAST) && $type == "array" && $enumType == "object") { + if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == "array" && $enumType == "object") { if ((object)$element == $enum) { return; } diff --git a/src/JsonSchema/Constraints/Factory.php b/src/JsonSchema/Constraints/Factory.php index d67fc952..71fea365 100644 --- a/src/JsonSchema/Constraints/Factory.php +++ b/src/JsonSchema/Constraints/Factory.php @@ -10,6 +10,7 @@ namespace JsonSchema\Constraints; use JsonSchema\Exception\InvalidArgumentException; +use JsonSchema\Exception\InvalidConfigException; use JsonSchema\SchemaStorage; use JsonSchema\SchemaStorageInterface; use JsonSchema\Uri\UriRetriever; @@ -33,7 +34,7 @@ class Factory /** * @var int */ - private $checkMode; + private $checkMode = Constraint::CHECK_MODE_NORMAL; /** * @var TypeCheck\TypeCheckInterface[] @@ -54,8 +55,7 @@ class Factory 'enum' => 'JsonSchema\Constraints\EnumConstraint', 'format' => 'JsonSchema\Constraints\FormatConstraint', 'schema' => 'JsonSchema\Constraints\SchemaConstraint', - 'validator' => 'JsonSchema\Validator', - 'coercer' => 'JsonSchema\Coerce' + 'validator' => 'JsonSchema\Validator' ); /** @@ -73,11 +73,58 @@ public function __construct( UriRetrieverInterface $uriRetriever = null, $checkMode = Constraint::CHECK_MODE_NORMAL ) { + // set provided config options + $this->setConfig($checkMode); + $this->uriRetriever = $uriRetriever ?: new UriRetriever; $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); + } + + /** + * Set config values + * + * @param int $checkMode Set checkMode options - does not preserve existing flags + */ + public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) + { $this->checkMode = $checkMode; } + /** + * Enable checkMode flags + * + * @param int $options + */ + public function addConfig($options) + { + $this->checkMode |= $options; + } + + /** + * Disable checkMode flags + * + * @param int $options + */ + public function removeConfig($options) + { + $this->checkMode &= ~$options; + } + + /** + * Get checkMode option + * + * @param int $options Options to get, if null then return entire bitmask + * + * @return int + */ + public function getConfig($options = null) + { + if ($options === null) { + return $this->checkMode; + } + return ($this->checkMode & $options); + } + /** * @return UriRetrieverInterface */ @@ -140,12 +187,4 @@ public function createInstanceFor($constraintName) return clone $this->instanceCache[$constraintName]; } - - /** - * @return int - */ - public function getCheckMode() - { - return $this->checkMode; - } } diff --git a/src/JsonSchema/Constraints/FormatConstraint.php b/src/JsonSchema/Constraints/FormatConstraint.php index f837dbf9..8dc9b77c 100644 --- a/src/JsonSchema/Constraints/FormatConstraint.php +++ b/src/JsonSchema/Constraints/FormatConstraint.php @@ -23,7 +23,7 @@ class FormatConstraint extends Constraint /** * {@inheritDoc} */ - public function check($element, $schema = null, JsonPointer $path = null, $i = null) + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if (!isset($schema->format)) { return; diff --git a/src/JsonSchema/Constraints/NumberConstraint.php b/src/JsonSchema/Constraints/NumberConstraint.php index a4a7517a..a4dd0639 100644 --- a/src/JsonSchema/Constraints/NumberConstraint.php +++ b/src/JsonSchema/Constraints/NumberConstraint.php @@ -22,7 +22,7 @@ class NumberConstraint extends Constraint /** * {@inheritDoc} */ - public function check($element, $schema = null, JsonPointer $path = null, $i = null) + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify minimum if (isset($schema->exclusiveMinimum)) { diff --git a/src/JsonSchema/Constraints/ObjectConstraint.php b/src/JsonSchema/Constraints/ObjectConstraint.php index 5e84ac4b..1b6a229a 100644 --- a/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/src/JsonSchema/Constraints/ObjectConstraint.php @@ -22,20 +22,7 @@ class ObjectConstraint extends Constraint /** * {@inheritDoc} */ - public function check($element, $definition = null, JsonPointer $path = null, $additionalProp = null, $patternProperties = null) - { - $this->_check($element, $definition, $path, $additionalProp, $patternProperties); - } - - /** - * {@inheritDoc} - */ - public function coerce(&$element, $definition = null, JsonPointer $path = null, $additionalProp = null, $patternProperties = null) - { - $this->_check($element, $definition, $path, $additionalProp, $patternProperties, true); - } - - protected function _check(&$element, $definition = null, JsonPointer $path = null, $additionalProp = null, $patternProperties = null, $coerce = false) + public function check(&$element, $definition = null, JsonPointer $path = null, $additionalProp = null, $patternProperties = null) { if ($element instanceof UndefinedConstraint) { return; @@ -48,7 +35,7 @@ protected function _check(&$element, $definition = null, JsonPointer $path = nul if ($definition) { // validate the definition properties - $this->validateDefinition($element, $definition, $path, $coerce); + $this->validateDefinition($element, $definition, $path); } // additional the element properties @@ -132,9 +119,8 @@ public function validateElement($element, $matches, $objectDefinition = null, Js * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition ObjectConstraint definition * @param JsonPointer|null $path Path? - * @param boolean $coerce Whether to coerce strings to expected types or not */ - public function validateDefinition(&$element, $objectDefinition = null, JsonPointer $path = null, $coerce = false) + public function validateDefinition(&$element, $objectDefinition = null, JsonPointer $path = null) { $undefinedConstraint = $this->factory->createInstanceFor('undefined'); @@ -144,7 +130,7 @@ public function validateDefinition(&$element, $objectDefinition = null, JsonPoin if (is_object($definition)) { // Undefined constraint will check for is_object() and quit if is not - so why pass it? - $this->checkUndefined($property, $definition, $path, $i, $coerce); + $this->checkUndefined($property, $definition, $path, $i); } } } diff --git a/src/JsonSchema/Constraints/SchemaConstraint.php b/src/JsonSchema/Constraints/SchemaConstraint.php index d8750841..8a2c6f34 100644 --- a/src/JsonSchema/Constraints/SchemaConstraint.php +++ b/src/JsonSchema/Constraints/SchemaConstraint.php @@ -23,15 +23,10 @@ class SchemaConstraint extends Constraint /** * {@inheritDoc} */ - public function check($element, $schema = null, JsonPointer $path = null, $i = null) - { - $this->_check($element, $schema, $path, $i); - } - - protected function _check(&$element, $schema = null, JsonPointer $path = null, $i = null, $coerce = false){ + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null){ if ($schema !== null) { // passed schema - $this->checkUndefined($element, $schema, $path, $i, $coerce); + $this->checkUndefined($element, $schema, $path, $i); } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { $inlineSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); if (is_array($inlineSchema)) { @@ -43,9 +38,4 @@ protected function _check(&$element, $schema = null, JsonPointer $path = null, $ throw new InvalidArgumentException('no schema found to verify against'); } } - - public function coerce(&$element, $schema = null, JsonPointer $path = null, $i = null) - { - $this->_check($element, $schema, $path, $i, true); - } } diff --git a/src/JsonSchema/Constraints/StringConstraint.php b/src/JsonSchema/Constraints/StringConstraint.php index 724a8103..3b83023a 100644 --- a/src/JsonSchema/Constraints/StringConstraint.php +++ b/src/JsonSchema/Constraints/StringConstraint.php @@ -22,7 +22,7 @@ class StringConstraint extends Constraint /** * {@inheritDoc} */ - public function check($element, $schema = null, JsonPointer $path = null, $i = null) + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify maxLength if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { diff --git a/src/JsonSchema/Constraints/TypeConstraint.php b/src/JsonSchema/Constraints/TypeConstraint.php index 37c3d791..0ee33a29 100644 --- a/src/JsonSchema/Constraints/TypeConstraint.php +++ b/src/JsonSchema/Constraints/TypeConstraint.php @@ -39,32 +39,19 @@ class TypeConstraint extends Constraint /** * {@inheritDoc} */ - public function check($value = null, $schema = null, JsonPointer $path = null, $i = null) - { - $this->_check($value, $schema, $path, $i); - } - - /** - * {@inheritDoc} - */ - public function coerce(&$value = null, $schema = null, JsonPointer $path = null, $i = null) - { - $this->_check($value, $schema, $path, $i, true); - } - - protected function _check(&$value = null, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + public function check(&$value = null, $schema = null, JsonPointer $path = null, $i = null) { $type = isset($schema->type) ? $schema->type : null; $isValid = false; $wording = array(); if (is_array($type)) { - $this->validateTypesArray($value, $type, $wording, $isValid, $path, $coerce); + $this->validateTypesArray($value, $type, $wording, $isValid, $path); } elseif (is_object($type)) { $this->checkUndefined($value, $type, $path); return; } else { - $isValid = $this->validateType($value, $type, $coerce); + $isValid = $this->validateType($value, $type); } if ($isValid === false) { @@ -88,8 +75,7 @@ protected function _check(&$value = null, $schema = null, JsonPointer $path = nu * @param boolean $isValid The current validation value * @param $path */ - protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, - $path, $coerce = false) { + protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path) { foreach ($type as $tp) { // $tp can be an object, if it's a schema instead of a simple type, validate it // with a new type constraint @@ -107,7 +93,7 @@ protected function validateTypesArray(&$value, array $type, &$validTypesWording, $this->validateTypeNameWording( $tp ); $validTypesWording[] = self::$wording[$tp]; if (!$isValid) { - $isValid = $this->validateType( $value, $tp, $coerce ); + $isValid = $this->validateType( $value, $tp); } } } @@ -157,13 +143,12 @@ protected function validateTypeNameWording( $type) { * * @param mixed $value Value to validate * @param string $type TypeConstraint to check against - * @param boolean $coerce Whether to coerce strings to expected types or not * * @return boolean * * @throws InvalidArgumentException */ - protected function validateType(&$value, $type, $coerce = false) + protected function validateType(&$value, $type) { //mostly the case for inline schema if (!$type) { @@ -182,6 +167,8 @@ protected function validateType(&$value, $type, $coerce = false) return $this->getTypeCheck()->isArray($value); } + $coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES); + if ('integer' === $type) { if ($coerce) { $value = $this->toInteger($value); diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 73e85a1c..51464c5d 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -20,18 +20,10 @@ */ class UndefinedConstraint extends Constraint { - /** - * {@inheritDoc} - */ - public function check($value, $schema = null, JsonPointer $path = null, $i = null){ - $this->_check($value, $schema, $path, $i); - } - - public function coerce(&$value, $schema = null, JsonPointer $path = null, $i = null){ - $this->_check($value, $schema, $path, $i, true); - } - - protected function _check(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false) + /** + * {@inheritDoc} + */ + public function check(&$value, $schema = null, JsonPointer $path = null, $i = null) { if (is_null($schema) || !is_object($schema)) { return; @@ -40,13 +32,13 @@ protected function _check(&$value, $schema = null, JsonPointer $path = null, $i $path = $this->incrementPath($path ?: new JsonPointer(''), $i); // check special properties - $this->validateCommonProperties($value, $schema, $path, $i, $coerce); + $this->validateCommonProperties($value, $schema, $path, $i); // check allOf, anyOf, and oneOf properties - $this->validateOfProperties($value, $schema, $path, '', $coerce); + $this->validateOfProperties($value, $schema, $path, ''); // check known types - $this->validateTypes($value, $schema, $path, $i, $coerce); + $this->validateTypes($value, $schema, $path, $i); } /** @@ -56,13 +48,12 @@ protected function _check(&$value, $schema = null, JsonPointer $path = null, $i * @param mixed $schema * @param JsonPointer $path * @param string $i - * @param boolean $coerce */ - public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = null, $coerce = false) + public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = null) { // check array if ($this->getTypeCheck()->isArray($value)) { - $this->checkArray($value, $schema, $path, $i, $coerce); + $this->checkArray($value, $schema, $path, $i); } // check object @@ -72,14 +63,13 @@ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = n isset($schema->properties) ? $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties) : $schema, $path, isset($schema->additionalProperties) ? $schema->additionalProperties : null, - isset($schema->patternProperties) ? $schema->patternProperties : null, - $coerce + isset($schema->patternProperties) ? $schema->patternProperties : null ); } // check string if (is_string($value)) { - $this->checkString($value, $schema, $path, $i, $coerce); + $this->checkString($value, $schema, $path, $i); } // check numeric @@ -100,9 +90,8 @@ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = n * @param mixed $schema * @param JsonPointer $path * @param string $i - * @param boolean $coerce */ - protected function validateCommonProperties(&$value, $schema = null, JsonPointer $path, $i = "", $coerce=false) + protected function validateCommonProperties(&$value, $schema = null, JsonPointer $path, $i = "") { // if it extends another schema, it must pass that schema as well if (isset($schema->extends)) { @@ -111,10 +100,10 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer } if (is_array($schema->extends)) { foreach ($schema->extends as $extends) { - $this->checkUndefined($value, $extends, $path, $i, $coerce); + $this->checkUndefined($value, $extends, $path, $i); } } else { - $this->checkUndefined($value, $schema->extends, $path, $i, $coerce); + $this->checkUndefined($value, $schema->extends, $path, $i); } } @@ -141,7 +130,7 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer // Verify type if (!($value instanceof UndefinedConstraint)) { - $this->checkType($value, $schema, $path, $i, $coerce); + $this->checkType($value, $schema, $path, $i); } // Verify disallowed items @@ -162,7 +151,7 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer if (isset($schema->not)) { $initErrors = $this->getErrors(); - $this->checkUndefined($value, $schema->not, $path, $i, $coerce); + $this->checkUndefined($value, $schema->not, $path, $i); // if no new errors were raised then the instance validated against the "not" schema if (count($this->getErrors()) == count($initErrors)) { @@ -185,9 +174,8 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer * @param mixed $schema * @param JsonPointer $path * @param string $i - * @param boolean $coerce */ - protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = "", $coerce = false) + protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = "") { // Verify type if ($value instanceof UndefinedConstraint) { @@ -198,7 +186,7 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $isValid = true; foreach ($schema->allOf as $allOf) { $initErrors = $this->getErrors(); - $this->checkUndefined($value, $allOf, $path, $i, $coerce); + $this->checkUndefined($value, $allOf, $path, $i); $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); } if (!$isValid) { @@ -211,7 +199,7 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $startErrors = $this->getErrors(); foreach ($schema->anyOf as $anyOf) { $initErrors = $this->getErrors(); - $this->checkUndefined($value, $anyOf, $path, $i, $coerce); + $this->checkUndefined($value, $anyOf, $path, $i); if ($isValid = (count($this->getErrors()) == count($initErrors))) { break; } @@ -229,7 +217,7 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $startErrors = $this->getErrors(); foreach ($schema->oneOf as $oneOf) { $this->errors = array(); - $this->checkUndefined($value, $oneOf, $path, $i, $coerce); + $this->checkUndefined($value, $oneOf, $path, $i); if (count($this->getErrors()) == 0) { $matchedSchemas++; } diff --git a/src/JsonSchema/Exception/InvalidConfigException.php b/src/JsonSchema/Exception/InvalidConfigException.php new file mode 100644 index 00000000..a48d6da0 --- /dev/null +++ b/src/JsonSchema/Exception/InvalidConfigException.php @@ -0,0 +1,17 @@ + * @see README.md */ -class Validator extends Constraint +class Validator extends BaseConstraint { const SCHEMA_MEDIA_TYPE = 'application/schema+json'; /** * Validates the given data against the schema and returns an object containing the results * Both the php object and the schema are supposed to be a result of a json_decode call. - * The validation works as defined by the schema proposal in http://json-schema.org + * The validation works as defined by the schema proposal in http://json-schema.org. + * + * Note that the first argument is passwd by reference, so you must pass in a variable. * * {@inheritDoc} */ - public function check($value, $schema = null, JsonPointer $path = null, $i = null) + public function validate(&$value, $schema = null, $checkMode = null) { + $initialCheckMode = $this->factory->getConfig(); + if ($checkMode !== null) { + $this->factory->setConfig($checkMode); + } + $validator = $this->factory->createInstanceFor('schema'); $validator->check($value, $schema); + $this->factory->setConfig($initialCheckMode); + $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); } /** - * Does everything that check does, but will also coerce string values in the input to their expected - * types defined in the schema whenever possible. Note that the first argument is passed by reference, - * so you must pass in a variable. - * - * {@inheritDoc} + * Alias to validate(), to maintain backwards-compatibility with the previous API */ - public function coerce(&$value, $schema = null, JsonPointer $path = null, $i = null) + public function check($value, $schema) { - $validator = $this->factory->createInstanceFor('schema'); - $validator->coerce($value, $schema); + return $this->validate($value, $schema); + } - $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); + /** + * Alias to validate(), to maintain backwards-compatibility with the previous API + */ + public function coerce(&$value, $schema) + { + return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); } } diff --git a/tests/Constraints/ArraysTest.php b/tests/Constraints/ArraysTest.php index 1fef244c..5498e35e 100644 --- a/tests/Constraints/ArraysTest.php +++ b/tests/Constraints/ArraysTest.php @@ -150,6 +150,22 @@ public function getValidTests() } } }' + ), + array( // test more schema items than array items + '{"data": [1, 2]}', + '{ + "type": "object", + "properties": { + "data": { + "type": "array", + "items": [ + {"type": "number"}, + {"type": "number"}, + {"type": "number"} + ] + } + } + }' ) ); } diff --git a/tests/Constraints/BaseTestCase.php b/tests/Constraints/BaseTestCase.php index c7d699bf..07f5636f 100644 --- a/tests/Constraints/BaseTestCase.php +++ b/tests/Constraints/BaseTestCase.php @@ -31,7 +31,8 @@ public function testInvalidCases($input, $schema, $checkMode = Constraint::CHECK $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage, null, $checkMode)); - $validator->check(json_decode($input), $schema); + $checkValue = json_decode($input); + $validator->validate($checkValue, $schema); if (array() !== $errors) { $this->assertEquals($errors, $validator->getErrors(), print_r($validator->getErrors(),true)); @@ -53,7 +54,8 @@ public function testInvalidCasesUsingAssoc($input, $schema, $checkMode = Constra $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage, null, $checkMode)); - $validator->check(json_decode($input, true), $schema); + $checkValue = json_decode($input, true); + $validator->validate($checkValue, $schema); if (array() !== $errors) { $this->assertEquals($errors, $validator->getErrors(), print_r($validator->getErrors(), true)); @@ -70,7 +72,8 @@ public function testValidCases($input, $schema, $checkMode = Constraint::CHECK_M $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage, null, $checkMode)); - $validator->check(json_decode($input), $schema); + $checkValue = json_decode($input); + $validator->validate($checkValue, $schema); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } @@ -91,7 +94,7 @@ public function testValidCasesUsingAssoc($input, $schema, $checkMode = Constrain $value = json_decode($input, true); $validator = new Validator(new Factory($schemaStorage, null, $checkMode)); - $validator->check($value, $schema); + $validator->validate($value, $schema); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } diff --git a/tests/Constraints/CoerciveTest.php b/tests/Constraints/CoerciveTest.php index 90b54a05..b8d134d5 100644 --- a/tests/Constraints/CoerciveTest.php +++ b/tests/Constraints/CoerciveTest.php @@ -22,7 +22,7 @@ class CoerciveTest extends BasicTypesTest */ public function testValidCoerceCasesUsingAssoc($input, $schema) { - $checkMode = Constraint::CHECK_MODE_TYPE_CAST; + $checkMode = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES; $schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema))); $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); @@ -31,7 +31,7 @@ public function testValidCoerceCasesUsingAssoc($input, $schema) $value = json_decode($input, true); - $validator->coerce($value, $schema); + $validator->validate($value, $schema, $checkMode); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } @@ -40,7 +40,7 @@ public function testValidCoerceCasesUsingAssoc($input, $schema) */ public function testValidCoerceCases($input, $schema, $errors = array()) { - $checkMode = Constraint::CHECK_MODE_TYPE_CAST; + $checkMode = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES; $schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema))); $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); @@ -52,7 +52,7 @@ public function testValidCoerceCases($input, $schema, $errors = array()) $this->assertTrue(gettype($value->integer) == "string"); $this->assertTrue(gettype($value->boolean) == "string"); - $validator->coerce($value, $schema); + $validator->validate($value, $schema, $checkMode); $this->assertTrue(gettype($value->number) == "double"); $this->assertTrue(gettype($value->integer) == "integer"); @@ -81,14 +81,14 @@ public function testValidCoerceCases($input, $schema, $errors = array()) */ public function testInvalidCoerceCases($input, $schema, $errors = array()) { - $checkMode = Constraint::CHECK_MODE_TYPE_CAST; + $checkMode = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES; $schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema))); $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage, null, $checkMode)); $value = json_decode($input); - $validator->coerce($value, $schema); + $validator->validate($value, $schema, $checkMode); if (array() !== $errors) { $this->assertEquals($errors, $validator->getErrors(), print_r($validator->getErrors(), true)); @@ -101,14 +101,14 @@ public function testInvalidCoerceCases($input, $schema, $errors = array()) */ public function testInvalidCoerceCasesUsingAssoc($input, $schema, $errors = array()) { - $checkMode = Constraint::CHECK_MODE_TYPE_CAST; + $checkMode = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES; $schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema))); $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage, null, $checkMode)); $value = json_decode($input, true); - $validator->coerce($value, $schema); + $validator->validate($value, $schema, $checkMode); if (array() !== $errors) { $this->assertEquals($errors, $validator->getErrors(), print_r($validator->getErrors(), true)); diff --git a/tests/Constraints/FactoryTest.php b/tests/Constraints/FactoryTest.php index 956db6de..59d3cf79 100644 --- a/tests/Constraints/FactoryTest.php +++ b/tests/Constraints/FactoryTest.php @@ -26,7 +26,7 @@ class MyBadConstraint {} * @package JsonSchema\Tests\Constraints */ class MyStringConstraint extends Constraint { - public function check($value, $schema = null, JsonPointer $path = null, $i = null){} + public function check(&$value, $schema = null, JsonPointer $path = null, $i = null){} } class FactoryTest extends TestCase @@ -69,7 +69,6 @@ public function constraintNameProvider() array('enum', 'JsonSchema\Constraints\EnumConstraint'), array('format', 'JsonSchema\Constraints\FormatConstraint'), array('schema', 'JsonSchema\Constraints\SchemaConstraint'), - array('validator', 'JsonSchema\Validator'), ); } @@ -114,4 +113,32 @@ public function testSetConstraintClassInstance() $this->assertInstanceOf('JsonSchema\Tests\Constraints\MyStringConstraint', $constraint); $this->assertInstanceOf('JsonSchema\Constraints\ConstraintInterface', $constraint); } + + public function testCheckMode() + { + $f = new Factory(); + + // test default value + $this->assertEquals(Constraint::CHECK_MODE_NORMAL, $f->getConfig()); + + // test overriding config + $f->setConfig(Constraint::CHECK_MODE_COERCE_TYPES); + $this->assertEquals(Constraint::CHECK_MODE_COERCE_TYPES, $f->getConfig()); + + // test adding config + $f->addConfig(Constraint::CHECK_MODE_NORMAL); + $this->assertEquals(Constraint::CHECK_MODE_NORMAL | Constraint::CHECK_MODE_COERCE_TYPES, $f->getConfig()); + + // test getting filtered config + $this->assertEquals(Constraint::CHECK_MODE_NORMAL, $f->getConfig(Constraint::CHECK_MODE_NORMAL)); + + // test removing config + $f->removeConfig(Constraint::CHECK_MODE_COERCE_TYPES); + $this->assertEquals(Constraint::CHECK_MODE_NORMAL, $f->getConfig()); + + // test resetting to defaults + $f->setConfig(Constraint::CHECK_MODE_COERCE_TYPES | Constraint::CHECK_MODE_TYPE_CAST); + $f->setConfig(); + $this->assertEquals(Constraint::CHECK_MODE_NORMAL, $f->getConfig()); + } } diff --git a/tests/Constraints/FormatTest.php b/tests/Constraints/FormatTest.php index c09decd9..d11d8c2c 100644 --- a/tests/Constraints/FormatTest.php +++ b/tests/Constraints/FormatTest.php @@ -23,7 +23,8 @@ public function testNullThing() $validator = new FormatConstraint(); $schema = new \stdClass; - $validator->check('10', $schema); + $checkValue = 10; + $validator->check($checkValue, $schema); $this->assertEmpty($validator->getErrors()); } @@ -34,15 +35,18 @@ public function testRegex() $schema->format = 'regex'; $validator->reset(); - $validator->check('\d+', $schema); + $checkValue = '\d+'; + $validator->check($checkValue, $schema); $this->assertEmpty($validator->getErrors()); $validator->reset(); - $validator->check('^(abc]', $schema); + $checkValue = '^(abc]'; + $validator->check($checkValue, $schema); $this->assertCount(1, $validator->getErrors()); $validator->reset(); - $validator->check('^猡猡獛$', $schema); + $checkValue = '^猡猡獛$'; + $validator->check($checkValue, $schema); $this->assertEmpty($validator->getErrors()); } diff --git a/tests/Constraints/LongArraysTest.php b/tests/Constraints/LongArraysTest.php index 9637eb6f..96c074f6 100644 --- a/tests/Constraints/LongArraysTest.php +++ b/tests/Constraints/LongArraysTest.php @@ -38,7 +38,8 @@ public function testLongStringArray() $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage)); - $validator->check(json_decode($input), $schema); + $checkValue = json_decode($input); + $validator->check($checkValue, $schema); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } @@ -65,7 +66,8 @@ public function testLongNumberArray() $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage)); - $validator->check(json_decode($input), $schema); + $checkValue = json_decode($input); + $validator->check($checkValue, $schema); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } @@ -92,7 +94,8 @@ public function testLongIntegerArray() $schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json'); $validator = new Validator(new Factory($schemaStorage)); - $validator->check(json_decode($input), $schema); + $checkValue = json_decode($input); + $validator->check($checkValue, $schema); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } diff --git a/tests/Constraints/PointerTest.php b/tests/Constraints/PointerTest.php index a48eae0d..ca378e3d 100644 --- a/tests/Constraints/PointerTest.php +++ b/tests/Constraints/PointerTest.php @@ -79,7 +79,8 @@ public function testVariousPointers() ); $validator = new Validator(); - $validator->check(json_decode(json_encode($value)), json_decode(json_encode($schema))); + $checkValue = json_decode(json_encode($value)); + $validator->check($checkValue, json_decode(json_encode($schema))); $this->assertEquals( array( diff --git a/tests/Constraints/SelfDefinedSchemaTest.php b/tests/Constraints/SelfDefinedSchemaTest.php index 8b0a9819..c49eef79 100644 --- a/tests/Constraints/SelfDefinedSchemaTest.php +++ b/tests/Constraints/SelfDefinedSchemaTest.php @@ -9,6 +9,8 @@ namespace JsonSchema\Tests\Constraints; +use \JsonSchema\Validator; + class SelfDefinedSchemaTest extends BaseTestCase { public function getInvalidTests() @@ -60,4 +62,11 @@ public function getValidTests() ) ); } + + public function testInvalidArgumentException() + { + $v = new Validator(); + $this->setExpectedException('\JsonSchema\Exception\InvalidArgumentException'); + $v->check(json_decode('{}'), json_decode('')); + } } diff --git a/tests/Uri/UriRetrieverTest.php b/tests/Uri/UriRetrieverTest.php index 3ac06b6a..11f21bd5 100644 --- a/tests/Uri/UriRetrieverTest.php +++ b/tests/Uri/UriRetrieverTest.php @@ -270,7 +270,7 @@ private function mockRetriever($schema) { $retrieverMock = $this->getRetrieverMock($schema); - $factory = new \ReflectionProperty('JsonSchema\Constraints\Constraint', 'factory'); + $factory = new \ReflectionProperty('JsonSchema\Constraints\BaseConstraint', 'factory'); $factory->setAccessible(true); $factory = $factory->getValue($this->validator);