From 3dba977b0f26591a5a6432765e124abdb4ad68b3 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Tue, 7 Mar 2017 07:21:05 -0800 Subject: [PATCH] centralize errors (#364) * centralize errors * isolate 'more' info * throw exception for missing error message * swap args --- composer.json | 3 +- src/JsonSchema/ConstraintError.php | 104 ++++++++++++++++++ src/JsonSchema/Constraints/BaseConstraint.php | 22 ++-- .../Constraints/CollectionConstraint.php | 16 ++- .../Constraints/ConstraintInterface.php | 10 +- src/JsonSchema/Constraints/EnumConstraint.php | 3 +- .../Constraints/FormatConstraint.php | 45 +++++--- .../Constraints/NumberConstraint.php | 21 ++-- .../Constraints/ObjectConstraint.php | 14 ++- .../Constraints/StringConstraint.php | 7 +- src/JsonSchema/Constraints/TypeConstraint.php | 7 +- .../Constraints/UndefinedConstraint.php | 30 +++-- .../Constraints/AdditionalPropertiesTest.php | 7 +- tests/Constraints/OfPropertiesTest.php | 21 +++- tests/Constraints/PointerTest.php | 28 ++++- 15 files changed, 268 insertions(+), 70 deletions(-) create mode 100644 src/JsonSchema/ConstraintError.php diff --git a/composer.json b/composer.json index f1f6faea..7707dfb7 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ } }], "require": { - "php": ">=5.3.3" + "php": ">=5.3.3", + "marc-mabe/php-enum":"2.3.1" }, "require-dev": { "json-schema/JSON-Schema-Test-Suite": "1.2.0", diff --git a/src/JsonSchema/ConstraintError.php b/src/JsonSchema/ConstraintError.php new file mode 100644 index 00000000..d7b73346 --- /dev/null +++ b/src/JsonSchema/ConstraintError.php @@ -0,0 +1,104 @@ +getValue(); + static $messages = array( + 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::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::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', + self::FORMAT_DATE => 'Invalid date %s, expected format YYYY-MM-DD', + self::FORMAT_DATE_TIME => 'Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', + self::FORMAT_DATE_UTC => 'Invalid time %s, expected integer of milliseconds since Epoch', + self::FORMAT_EMAIL => 'Invalid email', + self::FORMAT_HOSTNAME => 'Invalid hostname', + self::FORMAT_IP => 'Invalid IP address', + self::FORMAT_PHONE => 'Invalid phone number', + self::FORMAT_REGEX=> 'Invalid regex format %s', + self::FORMAT_STYLE => 'Invalid style', + self::FORMAT_TIME => 'Invalid time %s, expected format hh:mm:ss', + self::FORMAT_URL => 'Invalid URL format', + self::LENGTH_MAX => 'Must be at most %d characters long', + self::LENGTH_MIN => 'Must be at least %d characters long', + self::MAX_ITEMS => 'There must be a maximum of %d items in the array', + self::MAXIMUM => 'Must have a maximum value less than or equal to %d', + self::MIN_ITEMS => 'There must be a minimum of %d items in the array', + self::MINIMUM => 'Must have a minimum value greater than or equal to %d', + self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum', + self::MISSING_MINIMUM => 'Use of exclusiveMinimum requires presence of minimum', + self::MULTIPLE_OF => 'Must be a multiple of %d', + self::NOT => 'Matched a schema which it should not', + self::ONE_OF => 'Failed to match exactly one schema', + self::REQUIRED => 'The property %s is required', + self::REQUIRED_D3 => 'Is missing and it is required', + self::REQUIRES => 'The presence of the property %s requires that %s also be present', + self::PATTERN => 'Does not match the regex pattern %s', + self::PREGEX_INVALID => 'The pattern %s is invalid', + self::PROPERTIES_MIN => 'Must contain a minimum of %d properties', + self::PROPERTIES_MAX => 'Must contain no more than %d properties', + self::TYPE => '%s value found, but %s is required', + self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array' + ); + + if (!isset($messages[$name])) { + throw new InvalidArgumentException('Missing error message for ' . $name); + } + + return $messages[$name]; + } +} diff --git a/src/JsonSchema/Constraints/BaseConstraint.php b/src/JsonSchema/Constraints/BaseConstraint.php index ef1bdc54..bc608f6d 100644 --- a/src/JsonSchema/Constraints/BaseConstraint.php +++ b/src/JsonSchema/Constraints/BaseConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\ValidationException; @@ -36,23 +37,30 @@ public function __construct(Factory $factory = null) $this->factory = $factory ?: new Factory(); } - public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) + public function addError(ConstraintError $constraint, JsonPointer $path = null, array $more = array()) { + $message = $constraint ? $constraint->getMessage() : ''; + $name = $constraint ? $constraint->getValue() : ''; $error = array( 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), - 'message' => $message, - 'constraint' => $constraint, + 'message' => ucfirst(vsprintf($message, array_map(function ($val) { + if (is_scalar($val)) { + return $val; + } + + return json_encode($val); + }, array_values($more)))), + 'constraint' => array( + 'name' => $name, + 'params' => $more + ) ); if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); } - if (is_array($more) && count($more) > 0) { - $error += $more; - } - $this->errors[] = $error; } diff --git a/src/JsonSchema/Constraints/CollectionConstraint.php b/src/JsonSchema/Constraints/CollectionConstraint.php index 3c594b3c..a4227866 100644 --- a/src/JsonSchema/Constraints/CollectionConstraint.php +++ b/src/JsonSchema/Constraints/CollectionConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; /** @@ -26,12 +27,12 @@ public function check(&$value, $schema = null, JsonPointer $path = null, $i = nu { // Verify minItems if (isset($schema->minItems) && count($value) < $schema->minItems) { - $this->addError($path, 'There must be a minimum of ' . $schema->minItems . ' items in the array', 'minItems', array('minItems' => $schema->minItems)); + $this->addError(ConstraintError::MIN_ITEMS(), $path, array('minItems' => $schema->minItems)); } // Verify maxItems if (isset($schema->maxItems) && count($value) > $schema->maxItems) { - $this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems)); + $this->addError(ConstraintError::MAX_ITEMS(), $path, array('maxItems' => $schema->maxItems)); } // Verify uniqueItems @@ -43,7 +44,7 @@ public function check(&$value, $schema = null, JsonPointer $path = null, $i = nu }, $value); } if (count(array_unique($unique)) != count($value)) { - $this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems'); + $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); } } @@ -124,7 +125,14 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu $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)); + ConstraintError::ADDITIONAL_ITEMS(), + $path, + array( + 'item' => $i, + 'property' => $k, + 'additionalItems' => $schema->additionalItems + ) + ); } } else { // Should be valid against an empty schema diff --git a/src/JsonSchema/Constraints/ConstraintInterface.php b/src/JsonSchema/Constraints/ConstraintInterface.php index 442268e6..6007c099 100644 --- a/src/JsonSchema/Constraints/ConstraintInterface.php +++ b/src/JsonSchema/Constraints/ConstraintInterface.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; /** @@ -35,12 +36,11 @@ public function addErrors(array $errors); /** * adds an error * - * @param JsonPointer|null $path - * @param string $message - * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' - * @param array $more more array elements to add to the error + * @param ConstraintError $constraint the constraint/rule that is broken, e.g.: ConstraintErrors::LENGTH_MIN() + * @param JsonPointer |null $path + * @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(ConstraintError $constraint, JsonPointer $path = null, array $more = array()); /** * checks if the validator has not raised errors diff --git a/src/JsonSchema/Constraints/EnumConstraint.php b/src/JsonSchema/Constraints/EnumConstraint.php index 0fd2b6a0..5e401228 100644 --- a/src/JsonSchema/Constraints/EnumConstraint.php +++ b/src/JsonSchema/Constraints/EnumConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; /** @@ -49,6 +50,6 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i = } } - $this->addError($path, 'Does not have a value in the enumeration ' . json_encode($schema->enum), 'enum', array('enum' => $schema->enum)); + $this->addError(ConstraintError::ENUM(), $path, array('enum' => $schema->enum)); } } diff --git a/src/JsonSchema/Constraints/FormatConstraint.php b/src/JsonSchema/Constraints/FormatConstraint.php index ad192b5b..5fbd2c40 100644 --- a/src/JsonSchema/Constraints/FormatConstraint.php +++ b/src/JsonSchema/Constraints/FormatConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; use JsonSchema\Rfc3339; @@ -33,49 +34,67 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i = switch ($schema->format) { case 'date': if (!$date = $this->validateDateTime($element, 'Y-m-d')) { - $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_DATE(), $path, array( + 'date' => $element, + 'format' => $schema->format + ) + ); } break; case 'time': if (!$this->validateDateTime($element, 'H:i:s')) { - $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_TIME(), $path, array( + 'time' => json_encode($element), + 'format' => $schema->format, + ) + ); } break; case 'date-time': if (null === Rfc3339::createFromString($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)), 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, array( + 'dateTime' => json_encode($element), + 'format' => $schema->format + ) + ); } break; case 'utc-millisec': if (!$this->validateDateTime($element, 'U')) { - $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, array( + 'value' => $element, + 'format' => $schema->format)); } break; case 'regex': if (!$this->validateRegex($element)) { - $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_REGEX(), $path, array( + 'value' => $element, + 'format' => $schema->format + ) + ); } break; case 'color': if (!$this->validateColor($element)) { - $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_COLOR(), $path, array('format' => $schema->format)); } break; case 'style': if (!$this->validateStyle($element)) { - $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_STYLE(), $path, array('format' => $schema->format)); } break; case 'phone': if (!$this->validatePhone($element)) { - $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_PHONE(), $path, array('format' => $schema->format)); } break; @@ -99,34 +118,34 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i = $validURL = null; } if ($validURL === null) { - $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_URL(), $path, array('format' => $schema->format)); } } break; case 'email': if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) { - $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_EMAIL(), $path, array('format' => $schema->format)); } break; case 'ip-address': case 'ipv4': if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { - $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_IP(), $path, array('format' => $schema->format)); } break; case 'ipv6': if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { - $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_IP(), $path, array('format' => $schema->format)); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($element)) { - $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, array('format' => $schema->format)); } break; diff --git a/src/JsonSchema/Constraints/NumberConstraint.php b/src/JsonSchema/Constraints/NumberConstraint.php index 5a809774..aeea855d 100644 --- a/src/JsonSchema/Constraints/NumberConstraint.php +++ b/src/JsonSchema/Constraints/NumberConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; /** @@ -28,40 +29,40 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i = if (isset($schema->exclusiveMinimum)) { if (isset($schema->minimum)) { if ($schema->exclusiveMinimum && $element <= $schema->minimum) { - $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum)); + $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, array('minimum' => $schema->minimum)); } elseif ($element < $schema->minimum) { - $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); + $this->addError(ConstraintError::MINIMUM(), $path, array('minimum' => $schema->minimum)); } } else { - $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum'); + $this->addError(ConstraintError::MISSING_MINIMUM(), $path); } } elseif (isset($schema->minimum) && $element < $schema->minimum) { - $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); + $this->addError(ConstraintError::MINIMUM(), $path, array('minimum' => $schema->minimum)); } // Verify maximum if (isset($schema->exclusiveMaximum)) { if (isset($schema->maximum)) { if ($schema->exclusiveMaximum && $element >= $schema->maximum) { - $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum)); + $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, array('maximum' => $schema->maximum)); } elseif ($element > $schema->maximum) { - $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); + $this->addError(ConstraintError::MAXIMUM(), $path, array('maximum' => $schema->maximum)); } } else { - $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum'); + $this->addError(ConstraintError::MISSING_MAXIMUM(), $path); } } elseif (isset($schema->maximum) && $element > $schema->maximum) { - $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); + $this->addError(ConstraintError::MAXIMUM(), $path, array('maximum' => $schema->maximum)); } // Verify divisibleBy - Draft v3 if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { - $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); + $this->addError(ConstraintError::DIVISIBLE_BY(), $path, array('divisibleBy' => $schema->divisibleBy)); } // Verify multipleOf - Draft v4 if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { - $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf)); + $this->addError(ConstraintError::MULTIPLE_OF(), $path, array('multipleOf' => $schema->multipleOf)); } $this->checkFormat($element, $schema, $path, $i); diff --git a/src/JsonSchema/Constraints/ObjectConstraint.php b/src/JsonSchema/Constraints/ObjectConstraint.php index 5ea94f7d..d360a659 100644 --- a/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/src/JsonSchema/Constraints/ObjectConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; /** @@ -57,7 +58,7 @@ public function validatePatternProperties($element, JsonPointer $path = null, $p // Validate the pattern before using it to test for matches if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) { - $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex)); + $this->addError(ConstraintError::PREGEX_INVALID(), $path, array('pregex' => $pregex)); continue; } foreach ($element as $i => $value) { @@ -89,7 +90,7 @@ public function validateElement($element, $matches, $objectDefinition = null, Js // no additional properties allowed if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { - $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp'); + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, array('property' => $i)); } // additional properties defined @@ -104,7 +105,10 @@ public function validateElement($element, $matches, $objectDefinition = null, Js // property requires presence of another $require = $this->getProperty($definition, 'requires'); if ($require && !$this->getProperty($element, $require)) { - $this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires'); + $this->addError(ConstraintError::REQUIRES(), $path, array( + 'property' => $i, + 'requiredProperty' => $require + )); } $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); @@ -168,13 +172,13 @@ protected function validateMinMaxConstraint($element, $objectDefinition, JsonPoi // Verify minimum number of properties if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { - $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties)); + $this->addError(ConstraintError::PROPERTIES_MIN(), $path, array('minProperties' => $objectDefinition->minProperties)); } } // Verify maximum number of properties if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) { if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) { - $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties)); + $this->addError(ConstraintError::PROPERTIES_MAX(), $path, array('maxProperties' => $objectDefinition->maxProperties)); } } } diff --git a/src/JsonSchema/Constraints/StringConstraint.php b/src/JsonSchema/Constraints/StringConstraint.php index 5b15de7a..4790a040 100644 --- a/src/JsonSchema/Constraints/StringConstraint.php +++ b/src/JsonSchema/Constraints/StringConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; /** @@ -26,21 +27,21 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i = { // Verify maxLength if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { - $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array( + $this->addError(ConstraintError::LENGTH_MAX(), $path, array( 'maxLength' => $schema->maxLength, )); } //verify minLength if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { - $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array( + $this->addError(ConstraintError::LENGTH_MIN(), $path, array( 'minLength' => $schema->minLength, )); } // Verify a regex pattern if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { - $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array( + $this->addError(ConstraintError::PATTERN(), $path, array( 'pattern' => $schema->pattern, )); } diff --git a/src/JsonSchema/Constraints/TypeConstraint.php b/src/JsonSchema/Constraints/TypeConstraint.php index 0ef32843..a1db1d3a 100644 --- a/src/JsonSchema/Constraints/TypeConstraint.php +++ b/src/JsonSchema/Constraints/TypeConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use UnexpectedValueException as StandardUnexpectedValueException; @@ -60,8 +61,10 @@ public function check(&$value = null, $schema = null, JsonPointer $path = null, $this->validateTypeNameWording($type); $wording[] = self::$wording[$type]; } - $this->addError($path, ucwords(gettype($value)) . ' value found, but ' . - $this->implodeWith($wording, ', ', 'or') . ' is required', 'type'); + $this->addError(ConstraintError::TYPE(), $path, array( + 'expected' => gettype($value), + 'found' => $this->implodeWith($wording, ', ', 'or') + )); } } diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 147e5bc3..264a8d36 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -9,6 +9,7 @@ namespace JsonSchema\Constraints; +use JsonSchema\ConstraintError; use JsonSchema\Constraints\TypeCheck\LooseTypeCheck; use JsonSchema\Entity\JsonPointer; use JsonSchema\Uri\UriResolver; @@ -160,16 +161,17 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer foreach ($schema->required as $required) { if (!$this->getTypeCheck()->propertyExists($value, $required)) { $this->addError( - $this->incrementPath($path ?: new JsonPointer(''), $required), - 'The property ' . $required . ' is required', - 'required' + ConstraintError::REQUIRED(), + $this->incrementPath($path ?: new JsonPointer(''), $required), array( + 'property' => $required + ) ); } } } elseif (isset($schema->required) && !is_array($schema->required)) { // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} if ($schema->required && $value instanceof self) { - $this->addError($path, 'Is missing and it is required', 'required'); + $this->addError(ConstraintError::REQUIRED_D3(), $path); } } } @@ -189,7 +191,7 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer // if no new errors were raised it must be a disallowed value if (count($this->getErrors()) == count($initErrors)) { - $this->addError($path, 'Disallowed value was matched', 'disallow'); + $this->addError(ConstraintError::DISALLOW(), $path); } else { $this->errors = $initErrors; } @@ -201,7 +203,7 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer // if no new errors were raised then the instance validated against the "not" schema if (count($this->getErrors()) == count($initErrors)) { - $this->addError($path, 'Matched a schema which it should not', 'not'); + $this->addError(ConstraintError::NOT(), $path); } else { $this->errors = $initErrors; } @@ -236,7 +238,7 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); } if (!$isValid) { - $this->addError($path, 'Failed to match all schemas', 'allOf'); + $this->addError(ConstraintError::ALL_OF(), $path); } } @@ -251,7 +253,7 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i } } if (!$isValid) { - $this->addError($path, 'Failed to match at least one schema', 'anyOf'); + $this->addError(ConstraintError::ANY_OF(), $path); } else { $this->errors = $startErrors; } @@ -271,7 +273,7 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i } if ($matchedSchemas !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); - $this->addError($path, 'Failed to match exactly one schema', 'oneOf'); + $this->addError(ConstraintError::ONE_OF(), $path); } else { $this->errors = $startErrors; } @@ -293,13 +295,19 @@ protected function validateDependencies($value, $dependencies, JsonPointer $path if (is_string($dependency)) { // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { - $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies'); + $this->addError(ConstraintError::DEPENDENCIES(), $path, array( + 'key' => $key, + 'dependency' => $dependency + )); } } elseif (is_array($dependency)) { // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} foreach ($dependency as $d) { if (!$this->getTypeCheck()->propertyExists($value, $d)) { - $this->addError($path, "$key depends on $d and $d is missing", 'dependencies'); + $this->addError(ConstraintError::DEPENDENCIES(), $path, array( + 'key' => $key, + 'dependency' => $dependency + )); } } } elseif (is_object($dependency)) { diff --git a/tests/Constraints/AdditionalPropertiesTest.php b/tests/Constraints/AdditionalPropertiesTest.php index 4d68654b..5ecfa0a7 100644 --- a/tests/Constraints/AdditionalPropertiesTest.php +++ b/tests/Constraints/AdditionalPropertiesTest.php @@ -36,7 +36,12 @@ public function getInvalidTests() 'property' => '', 'pointer' => '', 'message' => 'The property additionalProp is not defined and the definition does not allow additional properties', - 'constraint' => 'additionalProp', + 'constraint' => array( + 'name' => 'additionalProp', + 'params' => array( + 'property' => 'additionalProp' + ) + ) ) ) ), diff --git a/tests/Constraints/OfPropertiesTest.php b/tests/Constraints/OfPropertiesTest.php index c36ba29e..721195f5 100644 --- a/tests/Constraints/OfPropertiesTest.php +++ b/tests/Constraints/OfPropertiesTest.php @@ -75,19 +75,34 @@ public function getInvalidTests() 'property' => 'prop2', 'pointer' => '/prop2', 'message' => 'Array value found, but a string is required', - 'constraint' => 'type', + 'constraint' => array( + 'name' => 'type', + 'params' => array( + 'expected' => 'array', + 'found' => 'a string' + ) + ) ), array( 'property' => 'prop2', 'pointer' => '/prop2', 'message' => 'Array value found, but a number is required', - 'constraint' => 'type', + 'constraint' => array( + 'name' => 'type', + 'params' => array( + 'expected' => 'array', + 'found' => 'a number' + ) + ) ), array( 'property' => 'prop2', 'pointer' => '/prop2', 'message' => 'Failed to match exactly one schema', - 'constraint' => 'oneOf', + 'constraint' => array( + 'name' => 'oneOf', + 'params' => array() + ) ), ), ), diff --git a/tests/Constraints/PointerTest.php b/tests/Constraints/PointerTest.php index ca378e3d..95c4c7b8 100644 --- a/tests/Constraints/PointerTest.php +++ b/tests/Constraints/PointerTest.php @@ -88,25 +88,45 @@ public function testVariousPointers() 'property' => 'prop1', 'pointer' => '/prop1', 'message' => 'The property prop1 is required', - 'constraint' => 'required' + 'constraint' => array( + 'name' => 'required', + 'params' => array( + 'property' => 'prop1' + ) + ) ), array( 'property' => 'prop2.prop2.1', 'pointer' => '/prop2/prop2.1', 'message' => 'The property prop2.1 is required', - 'constraint' => 'required' + 'constraint' => array( + 'name' => 'required', + 'params' => array( + 'property' => 'prop2.1' + ) + ) ), array( 'property' => 'prop3.prop3/1.prop3/1.1', 'pointer' => '/prop3/prop3~11/prop3~11.1', 'message' => 'The property prop3/1.1 is required', - 'constraint' => 'required' + 'constraint' => array( + 'name' => 'required', + 'params' => array( + 'property' => 'prop3/1.1' + ) + ) ), array( 'property' => 'prop4[0].prop4-child', 'pointer' => '/prop4/0/prop4-child', 'message' => 'The property prop4-child is required', - 'constraint' => 'required' + 'constraint' => array( + 'name' => 'required', + 'params' => array( + 'property' => 'prop4-child' + ) + ) ) ), $validator->getErrors()