Skip to content

Change error reporting for invalid types with multiple valid types #294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 84 additions & 33 deletions src/JsonSchema/Constraints/TypeConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,48 +41,99 @@ class TypeConstraint extends Constraint
public function check($value = null, $schema = null, $path = null, $i = null)
{
$type = isset($schema->type) ? $schema->type : null;
$isValid = true;
$isValid = false;
$wording = array();

if (is_array($type)) {
// @TODO refactor
$validatedOneType = false;
$errors = array();
foreach ($type as $tp) {
$validator = new static($this->checkMode);
$subSchema = new \stdClass();
$subSchema->type = $tp;
$validator->check($value, $subSchema, $path, null);
$error = $validator->getErrors();

if (!count($error)) {
$validatedOneType = true;
break;
}

$errors = $error;
}

if (!$validatedOneType) {
$this->addErrors($errors);

return;
}
$this->validateTypesArray($value, $type, $wording, $isValid, $path);
} elseif (is_object($type)) {
$this->checkUndefined($value, $type, $path);
return;
} else {
$isValid = $this->validateType($value, $type);
}

if ($isValid === false) {
if (!isset(self::$wording[$type])) {
throw new StandardUnexpectedValueException(
sprintf(
"No wording for %s available, expected wordings are: [%s]",
var_export($type, true),
implode(', ', array_filter(self::$wording)))
);
if (!is_array($type)) {
$this->validateTypeNameWording($type);
$wording[] = self::$wording[$type];
}
$this->addError($path, ucwords(gettype($value)) . " value found, but " .
$this->implodeWith($wording, ', ', 'or') . " is required", 'type');
}
}

/**
* Validates the given $value against the array of types in $type. Sets the value
* of $isValid to true, if at least one $type mateches the type of $value or the value
* passed as $isValid is already true.
*
* @param mixed $value Value to validate
* @param array $type TypeConstraints to check agains
* @param array $wording An array of wordings of the valid types of the array $type
* @param boolean $isValid The current validation value
*/
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
if (is_object($tp)) {
if (!$isValid) {
$validator = new static($this->checkMode);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we could get rid of this (at least for me) ugly looking workaround, but it seems it's the best solution we currently have :/

$subSchema = new \stdClass();
$subSchema->type = $tp;
$validator->check($value, $subSchema, $path, null);
$error = $validator->getErrors();
$isValid = !(bool)$error;
$validTypesWording[] = self::$wording['object'];
}
} else {
$this->validateTypeNameWording( $tp );
$validTypesWording[] = self::$wording[$tp];
if (!$isValid) {
$isValid = $this->validateType( $value, $tp );
}
}
$this->addError($path, ucwords(gettype($value)) . " value found, but " . self::$wording[$type] . " is required", 'type');
}
}

/**
* Implodes the given array like implode() with turned around parameters and with the
* difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
* $delimiter.
*
* @param array $elements The elements to implode
* @param string $delimiter The delimiter to use
* @param bool $listEnd The last delimiter to use (defaults to $delimiter)
* @return string
*/
protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false) {
if ($listEnd === false || !isset($elements[1])) {
return implode(', ', $elements);
}
$lastElement = array_slice($elements, -1);
$firsElements = join(', ', array_slice($elements, 0, -1));
$implodedElements = array_merge(array($firsElements), $lastElement);
return join(" $listEnd ", $implodedElements);
}

/**
* Validates the given $type, if there's an associated self::$wording. If not, throws an
* exception.
*
* @param string $type The type to validate
*
* @throws StandardUnexpectedValueException
*/
protected function validateTypeNameWording( $type) {
if (!isset(self::$wording[$type])) {
throw new StandardUnexpectedValueException(
sprintf(
"No wording for %s available, expected wordings are: [%s]",
var_export($type, true),
implode(', ', array_filter(self::$wording)))
);
}
}

Expand Down Expand Up @@ -126,7 +177,7 @@ protected function validateType($value, $type)
if ('string' === $type) {
return is_string($value);
}

if ('email' === $type) {
return is_string($value);
}
Expand Down
19 changes: 11 additions & 8 deletions tests/Constraints/TypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ class TypeTest extends \PHPUnit_Framework_TestCase
public function provideIndefiniteArticlesForTypes()
{
return array(
array('integer', 'an',),
array('number', 'a',),
array('boolean', 'a',),
array('object', 'an',),
array('array', 'an',),
array('string', 'a',),
array('null', 'a', array(), 'array',),
array('integer', 'an integer',),
array('number', 'a number',),
array('boolean', 'a boolean',),
array('object', 'an object',),
array('array', 'an array',),
array('string', 'a string',),
array('null', 'a null', array(), 'array',),
array(array('string', 'boolean', 'integer'), 'a string, a boolean or an integer',),
array(array('string', 'boolean'), 'a string or a boolean',),
array(array('string'), 'a string',),
);
}

Expand All @@ -43,7 +46,7 @@ public function testIndefiniteArticleForTypeInTypeCheckErrorMessage($type, $word
{
$constraint = new TypeConstraint();
$constraint->check($value, (object)array('type' => $type));
$this->assertTypeConstraintError(ucwords($label)." value found, but $wording $type is required", $constraint);
$this->assertTypeConstraintError(ucwords($label)." value found, but $wording is required", $constraint);
}

/**
Expand Down