* Added two optional arguments to addError method to make it easier for custom error messages

* Added more information to the error message of StringConstraint
* Added more information to the Constraint addError
* Unknown format does not generate error
* Updated the test so that unknown format does not throw an error
* Make messages to start with a capital letter consistently
This commit is contained in:
sid 2015-03-30 01:39:32 +13:00 committed by sid
parent a4bee9f4b3
commit acdaf80a9c
14 changed files with 90 additions and 64 deletions

View File

@ -24,12 +24,12 @@ class CollectionConstraint extends Constraint
{
// 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");
$this->addError($path, "There must be a minimum of " . $schema->minItems . " items in the array", 'minItems', 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");
$this->addError($path, "There must be a maximum of " . $schema->maxItems . " items in the array", 'maxItems', array('maxItems' => $schema->maxItems,));
}
// Verify uniqueItems
@ -39,7 +39,7 @@ class CollectionConstraint extends Constraint
$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");
$this->addError($path, "There are no duplicates allowed in the array", 'uniqueItems');
}
}
@ -92,7 +92,7 @@ class CollectionConstraint extends Constraint
$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');
$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

View File

@ -61,12 +61,20 @@ abstract class Constraint implements ConstraintInterface
/**
* {@inheritDoc}
*/
public function addError($path, $message)
public function addError($path, $message, $constraint='', array $more=null)
{
$this->errors[] = array(
$error = array(
'property' => $path,
'message' => $message
'message' => $message,
'constraint' => $constraint,
);
if (is_array($more) && count($more) > 0)
{
$error += $more;
}
$this->errors[] = $error;
}
/**

View File

@ -33,10 +33,12 @@ interface ConstraintInterface
/**
* adds an error
*
* @param $path
* @param $message
* @param string $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
*/
public function addError($path, $message);
public function addError($path, $message, $constraint='', array $more=null);
/**
* checks if the validator has not raised errors

View File

@ -41,6 +41,6 @@ class EnumConstraint extends Constraint
}
}
$this->addError($path, "Does not have a value in the enumeration " . print_r($schema->enum, true));
$this->addError($path, "Does not have a value in the enumeration " . print_r($schema->enum, true), 'enum', array('enum' => $schema->enum,));
}
}

View File

@ -29,13 +29,13 @@ class FormatConstraint extends Constraint
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)));
$this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('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)));
$this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format,));
}
break;
@ -45,74 +45,79 @@ class FormatConstraint extends Constraint
!$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)), 'format', array('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)));
$this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format,));
}
break;
case 'regex':
if (!$this->validateRegex($element)) {
$this->addError($path, 'Invalid regex format ' . $element);
$this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format,));
}
break;
case 'color':
if (!$this->validateColor($element)) {
$this->addError($path, "Invalid color");
$this->addError($path, "Invalid color", 'format', array('format' => $schema->format,));
}
break;
case 'style':
if (!$this->validateStyle($element)) {
$this->addError($path, "Invalid style");
$this->addError($path, "Invalid style", 'format', array('format' => $schema->format,));
}
break;
case 'phone':
if (!$this->validatePhone($element)) {
$this->addError($path, "Invalid phone number");
$this->addError($path, "Invalid phone number", 'format', array('format' => $schema->format,));
}
break;
case 'uri':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
$this->addError($path, "Invalid URL format");
$this->addError($path, "Invalid URL format", 'format', array('format' => $schema->format,));
}
break;
case 'email':
if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) {
$this->addError($path, "Invalid email");
$this->addError($path, "Invalid email", 'format', 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");
$this->addError($path, "Invalid IP address", 'format', 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");
$this->addError($path, "Invalid IP address", 'format', array('format' => $schema->format,));
}
break;
case 'host-name':
case 'hostname':
if (!$this->validateHostname($element)) {
$this->addError($path, "Invalid hostname");
$this->addError($path, "Invalid hostname", 'format', array('format' => $schema->format,));
}
break;
default:
// Do nothing so that custom formats can be used.
// Empty as it should be:
// The value of this keyword is called a format attribute. It MUST be a string.
// A format attribute can generally only validate a given set of instance types.
// If the type of the instance to validate is not in this set, validation for
// this format attribute and instance SHOULD succeed.
// http://json-schema.org/latest/json-schema-validation.html#anchor105
break;
}
}

View File

@ -26,40 +26,40 @@ class NumberConstraint extends Constraint
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);
$this->addError($path, "Must have a minimum value greater than boundary value of " . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum,));
} else if ($element < $schema->minimum) {
$this->addError($path, "Must have a minimum value of " . $schema->minimum);
$this->addError($path, "Must have a minimum value of " . $schema->minimum, 'minimum', array('minimum' => $schema->minimum,));
}
} else {
$this->addError($path, "Use of exclusiveMinimum requires presence of minimum");
$this->addError($path, "Use of exclusiveMinimum requires presence of minimum", 'missingMinimum');
}
} else if (isset($schema->minimum) && $element < $schema->minimum) {
$this->addError($path, "Must have a minimum value of " . $schema->minimum);
$this->addError($path, "Must have a minimum value of " . $schema->minimum, 'minimum', 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 less than boundary value of " . $schema->maximum);
$this->addError($path, "Must have a maximum value less than boundary value of " . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum,));
} else if ($element > $schema->maximum) {
$this->addError($path, "Must have a maximum value of " . $schema->maximum);
$this->addError($path, "Must have a maximum value of " . $schema->maximum, 'maximum', array('maximum' => $schema->maximum,));
}
} else {
$this->addError($path, "Use of exclusiveMaximum requires presence of maximum");
$this->addError($path, "Use of exclusiveMaximum requires presence of maximum", 'missingMinimum');
}
} else if (isset($schema->maximum) && $element > $schema->maximum) {
$this->addError($path, "Must have a maximum value of " . $schema->maximum);
$this->addError($path, "Must have a maximum value of " . $schema->maximum, 'maximum', 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);
$this->addError($path, "Is not divisible by " . $schema->divisibleBy, 'divisibleBy', 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);
$this->addError($path, "Must be a multiple of " . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf,));
}
$this->checkFormat($element, $schema, $path, $i);

View File

@ -46,7 +46,7 @@ class ObjectConstraint extends Constraint
foreach ($patternProperties as $pregex => $schema) {
// Validate the pattern before using it to test for matches
if (@preg_match('/'. $pregex . '/', '') === false) {
$this->addError($path, 'The pattern "' . $pregex . '" is invalid');
$this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex,));
continue;
}
foreach ($element as $i => $value) {
@ -77,7 +77,7 @@ class ObjectConstraint extends Constraint
// 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");
$this->addError($path, "The property " . $i . " is not defined and the definition does not allow additional properties", 'additionalProp');
}
// additional properties defined
@ -92,7 +92,7 @@ class ObjectConstraint extends Constraint
// 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");
$this->addError($path, "The presence of the property " . $i . " requires that " . $require . " also be present", 'requires');
}
if (!$definition) {

View File

@ -24,17 +24,23 @@ class StringConstraint extends Constraint
{
// Verify maxLength
if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
$this->addError($path, "Must be at most " . $schema->maxLength . " characters long");
$this->addError($path, "Must be at most " . $schema->maxLength . " characters long", 'maxLength', 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");
$this->addError($path, "Must be at least " . $schema->minLength . " characters long", 'minLength', array(
'minLength' => $schema->minLength,
));
}
// Verify a regex pattern
if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#', $element)) {
$this->addError($path, "Does not match the regex pattern " . $schema->pattern);
$this->addError($path, "Does not match the regex pattern " . $schema->pattern, 'pattern', array(
'pattern' => $schema->pattern,
));
}
$this->checkFormat($element, $schema, $path, $i);

View File

@ -82,7 +82,7 @@ class TypeConstraint extends Constraint
implode(', ', array_filter(self::$wording)))
);
}
$this->addError($path, gettype($value) . " value found, but " . self::$wording[$type] . " is required");
$this->addError($path, ucwords(gettype($value)) . " value found, but " . self::$wording[$type] . " is required", 'type');
}
}

View File

@ -117,18 +117,17 @@ class UndefinedConstraint extends Constraint
// Verify required values
if (is_object($value)) {
if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($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($required, "The property " . $required . " is required");
$this->addError($required, "The property " . $required . " is required", 'required');
}
}
} else if (isset($schema->required) && !is_array($schema->required)) {
// Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
if ( $schema->required && $value instanceof UndefinedConstraint) {
$this->addError($path, "Is missing and it is required");
$this->addError($path, "Is missing and it is required", 'required');
}
}
}
@ -148,7 +147,7 @@ class UndefinedConstraint extends Constraint
// 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");
$this->addError($path, "Disallowed value was matched", 'disallow');
} else {
$this->errors = $initErrors;
}
@ -160,7 +159,7 @@ class UndefinedConstraint extends Constraint
// 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");
$this->addError($path, "Matched a schema which it should not", 'not');
} else {
$this->errors = $initErrors;
}
@ -170,12 +169,12 @@ class UndefinedConstraint extends Constraint
if (is_object($value)) {
if (isset($schema->minProperties)) {
if (count(get_object_vars($value)) < $schema->minProperties) {
$this->addError($path, "Must contain a minimum of " . $schema->minProperties . " properties");
$this->addError($path, "Must contain a minimum of " . $schema->minProperties . " properties", 'minProperties', array('minProperties' => $schema->minProperties,));
}
}
if (isset($schema->maxProperties)) {
if (count(get_object_vars($value)) > $schema->maxProperties) {
$this->addError($path, "Must contain no more than " . $schema->maxProperties . " properties");
$this->addError($path, "Must contain no more than " . $schema->maxProperties . " properties", 'maxProperties', array('maxProperties' => $schema->maxProperties,));
}
}
}
@ -209,7 +208,7 @@ class UndefinedConstraint extends Constraint
$isValid = $isValid && (count($this->getErrors()) == count($initErrors));
}
if (!$isValid) {
$this->addError($path, "Failed to match all schemas");
$this->addError($path, "Failed to match all schemas", 'allOf');
}
}
@ -224,7 +223,7 @@ class UndefinedConstraint extends Constraint
}
}
if (!$isValid) {
$this->addError($path, "Failed to match at least one schema");
$this->addError($path, "Failed to match at least one schema", 'anyOf');
} else {
$this->errors = $startErrors;
}
@ -248,7 +247,8 @@ class UndefinedConstraint extends Constraint
$allErrors,
array(array(
'property' => $path,
'message' => "failed to match exactly one schema"
'message' => "Failed to match exactly one schema",
'constraint' => 'oneOf',
),),
$startErrors
)
@ -274,13 +274,13 @@ class UndefinedConstraint extends Constraint
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");
$this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies');
}
} 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");
$this->addError($path, "$key depends on $d and $d is missing", 'dependencies');
}
}
} else if (is_object($dependency)) {

View File

@ -35,8 +35,9 @@ class AdditionalPropertiesTest extends BaseTestCase
null,
array(
array(
'property' => '',
'message' => 'The property - additionalProp - is not defined and the definition does not allow additional properties'
'property' => '',
'message' => 'The property additionalProp is not defined and the definition does not allow additional properties',
'constraint' => 'additionalProp',
)
)
),

View File

@ -120,6 +120,7 @@ class FormatTest extends BaseTestCase
array('www.example.com', 'host-name'),
array('anything', '*'),
array('unknown', '*'),
);
}
@ -160,6 +161,7 @@ class FormatTest extends BaseTestCase
array(':::ff', 'ipv6'),
array('localhost', 'host-name'),
);
}

View File

@ -74,17 +74,19 @@ class OfPropertiesTest extends BaseTestCase
Validator::CHECK_MODE_NORMAL,
array(
array(
"property" => "prop2",
"message" => "array value found, but a string is required",
"property" => "prop2",
"message" => "Array value found, but a string is required",
"constraint" => "type",
),
array(
"property" => "prop2",
"message" => "array value found, but a number is required",
"property" => "prop2",
"message" => "Array value found, but a number is required",
"constraint" => "type",
),
array(
"property" => "prop2",
"message" => "failed to match exactly one schema",
"property" => "prop2",
"message" => "Failed to match exactly one schema",
"constraint" => "oneOf",
),
),
),

View File

@ -43,7 +43,7 @@ class TypeTest extends \PHPUnit_Framework_TestCase
{
$constraint = new TypeConstraint();
$constraint->check($value, (object)array('type' => $type));
$this->assertTypeConstraintError("$label value found, but $wording $type is required", $constraint);
$this->assertTypeConstraintError(ucwords($label)." value found, but $wording $type is required", $constraint);
}
/**