* 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 // Verify minItems
if (isset($schema->minItems) && count($value) < $schema->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 // Verify maxItems
if (isset($schema->maxItems) && count($value) > $schema->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 // Verify uniqueItems
@ -39,7 +39,7 @@ class CollectionConstraint extends Constraint
$unique = array_map(function($e) { return var_export($e, true); }, $value); $unique = array_map(function($e) { return var_export($e, true); }, $value);
} }
if (count(array_unique($unique)) != count($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); $this->checkUndefined($v, $schema->additionalItems, $path, $k);
} else { } else {
$this->addError( $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 { } else {
// Should be valid against an empty schema // Should be valid against an empty schema

View File

@ -61,12 +61,20 @@ abstract class Constraint implements ConstraintInterface
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function addError($path, $message) public function addError($path, $message, $constraint='', array $more=null)
{ {
$this->errors[] = array( $error = array(
'property' => $path, '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 * adds an error
* *
* @param $path * @param string $path
* @param $message * @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 * 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) { switch ($schema->format) {
case 'date': case 'date':
if (!$date = $this->validateDateTime($element, 'Y-m-d')) { 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; break;
case 'time': case 'time':
if (!$this->validateDateTime($element, 'H:i:s')) { 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; 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:sP') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:sO') !$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; break;
case 'utc-millisec': case 'utc-millisec':
if (!$this->validateDateTime($element, 'U')) { 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; break;
case 'regex': case 'regex':
if (!$this->validateRegex($element)) { if (!$this->validateRegex($element)) {
$this->addError($path, 'Invalid regex format ' . $element); $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format,));
} }
break; break;
case 'color': case 'color':
if (!$this->validateColor($element)) { if (!$this->validateColor($element)) {
$this->addError($path, "Invalid color"); $this->addError($path, "Invalid color", 'format', array('format' => $schema->format,));
} }
break; break;
case 'style': case 'style':
if (!$this->validateStyle($element)) { if (!$this->validateStyle($element)) {
$this->addError($path, "Invalid style"); $this->addError($path, "Invalid style", 'format', array('format' => $schema->format,));
} }
break; break;
case 'phone': case 'phone':
if (!$this->validatePhone($element)) { if (!$this->validatePhone($element)) {
$this->addError($path, "Invalid phone number"); $this->addError($path, "Invalid phone number", 'format', array('format' => $schema->format,));
} }
break; break;
case 'uri': case 'uri':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { 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; break;
case 'email': case 'email':
if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) { 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; break;
case 'ip-address': case 'ip-address':
case 'ipv4': case 'ipv4':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_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; break;
case 'ipv6': case 'ipv6':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_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; break;
case 'host-name': case 'host-name':
case 'hostname': case 'hostname':
if (!$this->validateHostname($element)) { if (!$this->validateHostname($element)) {
$this->addError($path, "Invalid hostname"); $this->addError($path, "Invalid hostname", 'format', array('format' => $schema->format,));
} }
break; break;
default: 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; break;
} }
} }

View File

@ -26,40 +26,40 @@ class NumberConstraint extends Constraint
if (isset($schema->exclusiveMinimum)) { if (isset($schema->exclusiveMinimum)) {
if (isset($schema->minimum)) { if (isset($schema->minimum)) {
if ($schema->exclusiveMinimum && $element === $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) { } 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 { } 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) { } 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 // Verify maximum
if (isset($schema->exclusiveMaximum)) { if (isset($schema->exclusiveMaximum)) {
if (isset($schema->maximum)) { if (isset($schema->maximum)) {
if ($schema->exclusiveMaximum && $element === $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) { } 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 { } 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) { } 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 // Verify divisibleBy - Draft v3
if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { 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 // Verify multipleOf - Draft v4
if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { 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); $this->checkFormat($element, $schema, $path, $i);

View File

@ -46,7 +46,7 @@ class ObjectConstraint extends Constraint
foreach ($patternProperties as $pregex => $schema) { foreach ($patternProperties as $pregex => $schema) {
// Validate the pattern before using it to test for matches // Validate the pattern before using it to test for matches
if (@preg_match('/'. $pregex . '/', '') === false) { 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; continue;
} }
foreach ($element as $i => $value) { foreach ($element as $i => $value) {
@ -77,7 +77,7 @@ class ObjectConstraint extends Constraint
// no additional properties allowed // no additional properties allowed
if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { 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 // additional properties defined
@ -92,7 +92,7 @@ class ObjectConstraint extends Constraint
// property requires presence of another // property requires presence of another
$require = $this->getProperty($definition, 'requires'); $require = $this->getProperty($definition, 'requires');
if ($require && !$this->getProperty($element, $require)) { 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) { if (!$definition) {

View File

@ -24,17 +24,23 @@ class StringConstraint extends Constraint
{ {
// Verify maxLength // Verify maxLength
if (isset($schema->maxLength) && $this->strlen($element) > $schema->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 //verify minLength
if (isset($schema->minLength) && $this->strlen($element) < $schema->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 // Verify a regex pattern
if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#', $element)) { 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); $this->checkFormat($element, $schema, $path, $i);

View File

@ -82,7 +82,7 @@ class TypeConstraint extends Constraint
implode(', ', array_filter(self::$wording))) 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 // Verify required values
if (is_object($value)) { if (is_object($value)) {
if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required) ) { if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required) ) {
// Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
foreach ($schema->required as $required) { foreach ($schema->required as $required) {
if (!property_exists($value, $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)) { } else if (isset($schema->required) && !is_array($schema->required)) {
// Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
if ( $schema->required && $value instanceof UndefinedConstraint) { 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 no new errors were raised it must be a disallowed value
if (count($this->getErrors()) == count($initErrors)) { if (count($this->getErrors()) == count($initErrors)) {
$this->addError($path, "Disallowed value was matched"); $this->addError($path, "Disallowed value was matched", 'disallow');
} else { } else {
$this->errors = $initErrors; $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 no new errors were raised then the instance validated against the "not" schema
if (count($this->getErrors()) == count($initErrors)) { 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 { } else {
$this->errors = $initErrors; $this->errors = $initErrors;
} }
@ -170,12 +169,12 @@ class UndefinedConstraint extends Constraint
if (is_object($value)) { if (is_object($value)) {
if (isset($schema->minProperties)) { if (isset($schema->minProperties)) {
if (count(get_object_vars($value)) < $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 (isset($schema->maxProperties)) {
if (count(get_object_vars($value)) > $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)); $isValid = $isValid && (count($this->getErrors()) == count($initErrors));
} }
if (!$isValid) { 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) { if (!$isValid) {
$this->addError($path, "Failed to match at least one schema"); $this->addError($path, "Failed to match at least one schema", 'anyOf');
} else { } else {
$this->errors = $startErrors; $this->errors = $startErrors;
} }
@ -248,7 +247,8 @@ class UndefinedConstraint extends Constraint
$allErrors, $allErrors,
array(array( array(array(
'property' => $path, 'property' => $path,
'message' => "failed to match exactly one schema" 'message' => "Failed to match exactly one schema",
'constraint' => 'oneOf',
),), ),),
$startErrors $startErrors
) )
@ -274,13 +274,13 @@ class UndefinedConstraint extends Constraint
if (is_string($dependency)) { if (is_string($dependency)) {
// Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
if (!property_exists($value, $dependency)) { 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)) { } else if (is_array($dependency)) {
// Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
foreach ($dependency as $d) { foreach ($dependency as $d) {
if (!property_exists($value, $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)) { } else if (is_object($dependency)) {

View File

@ -35,8 +35,9 @@ class AdditionalPropertiesTest extends BaseTestCase
null, null,
array( array(
array( array(
'property' => '', 'property' => '',
'message' => 'The property - additionalProp - is not defined and the definition does not allow additional properties' '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('www.example.com', 'host-name'),
array('anything', '*'), array('anything', '*'),
array('unknown', '*'),
); );
} }
@ -160,6 +161,7 @@ class FormatTest extends BaseTestCase
array(':::ff', 'ipv6'), array(':::ff', 'ipv6'),
array('localhost', 'host-name'), array('localhost', 'host-name'),
); );
} }

View File

@ -74,17 +74,19 @@ class OfPropertiesTest extends BaseTestCase
Validator::CHECK_MODE_NORMAL, Validator::CHECK_MODE_NORMAL,
array( array(
array( array(
"property" => "prop2", "property" => "prop2",
"message" => "array value found, but a string is required", "message" => "Array value found, but a string is required",
"constraint" => "type",
), ),
array( array(
"property" => "prop2", "property" => "prop2",
"message" => "array value found, but a number is required", "message" => "Array value found, but a number is required",
"constraint" => "type",
), ),
array( array(
"property" => "prop2", "property" => "prop2",
"message" => "failed to match exactly one schema", "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 = new TypeConstraint();
$constraint->check($value, (object)array('type' => $type)); $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);
} }
/** /**