Skip to content

Commit 38503c4

Browse files
authored
Merge pull request #294 from FlorianSW/issue/293
Change error reporting for invalid types with multiple valid types
2 parents ddabcf8 + 53a8486 commit 38503c4

File tree

2 files changed

+95
-41
lines changed

2 files changed

+95
-41
lines changed

src/JsonSchema/Constraints/TypeConstraint.php

+84-33
Original file line numberDiff line numberDiff line change
@@ -41,48 +41,99 @@ class TypeConstraint extends Constraint
4141
public function check($value = null, $schema = null, $path = null, $i = null)
4242
{
4343
$type = isset($schema->type) ? $schema->type : null;
44-
$isValid = true;
44+
$isValid = false;
45+
$wording = array();
4546

4647
if (is_array($type)) {
47-
// @TODO refactor
48-
$validatedOneType = false;
49-
$errors = array();
50-
foreach ($type as $tp) {
51-
$validator = new static($this->checkMode);
52-
$subSchema = new \stdClass();
53-
$subSchema->type = $tp;
54-
$validator->check($value, $subSchema, $path, null);
55-
$error = $validator->getErrors();
56-
57-
if (!count($error)) {
58-
$validatedOneType = true;
59-
break;
60-
}
61-
62-
$errors = $error;
63-
}
64-
65-
if (!$validatedOneType) {
66-
$this->addErrors($errors);
67-
68-
return;
69-
}
48+
$this->validateTypesArray($value, $type, $wording, $isValid, $path);
7049
} elseif (is_object($type)) {
7150
$this->checkUndefined($value, $type, $path);
51+
return;
7252
} else {
7353
$isValid = $this->validateType($value, $type);
7454
}
7555

7656
if ($isValid === false) {
77-
if (!isset(self::$wording[$type])) {
78-
throw new StandardUnexpectedValueException(
79-
sprintf(
80-
"No wording for %s available, expected wordings are: [%s]",
81-
var_export($type, true),
82-
implode(', ', array_filter(self::$wording)))
83-
);
57+
if (!is_array($type)) {
58+
$this->validateTypeNameWording($type);
59+
$wording[] = self::$wording[$type];
60+
}
61+
$this->addError($path, ucwords(gettype($value)) . " value found, but " .
62+
$this->implodeWith($wording, ', ', 'or') . " is required", 'type');
63+
}
64+
}
65+
66+
/**
67+
* Validates the given $value against the array of types in $type. Sets the value
68+
* of $isValid to true, if at least one $type mateches the type of $value or the value
69+
* passed as $isValid is already true.
70+
*
71+
* @param mixed $value Value to validate
72+
* @param array $type TypeConstraints to check agains
73+
* @param array $wording An array of wordings of the valid types of the array $type
74+
* @param boolean $isValid The current validation value
75+
*/
76+
protected function validateTypesArray($value, array $type, &$validTypesWording, &$isValid,
77+
$path) {
78+
foreach ($type as $tp) {
79+
// $tp can be an object, if it's a schema instead of a simple type, validate it
80+
// with a new type constraint
81+
if (is_object($tp)) {
82+
if (!$isValid) {
83+
$validator = new static($this->checkMode);
84+
$subSchema = new \stdClass();
85+
$subSchema->type = $tp;
86+
$validator->check($value, $subSchema, $path, null);
87+
$error = $validator->getErrors();
88+
$isValid = !(bool)$error;
89+
$validTypesWording[] = self::$wording['object'];
90+
}
91+
} else {
92+
$this->validateTypeNameWording( $tp );
93+
$validTypesWording[] = self::$wording[$tp];
94+
if (!$isValid) {
95+
$isValid = $this->validateType( $value, $tp );
96+
}
8497
}
85-
$this->addError($path, ucwords(gettype($value)) . " value found, but " . self::$wording[$type] . " is required", 'type');
98+
}
99+
}
100+
101+
/**
102+
* Implodes the given array like implode() with turned around parameters and with the
103+
* difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
104+
* $delimiter.
105+
*
106+
* @param array $elements The elements to implode
107+
* @param string $delimiter The delimiter to use
108+
* @param bool $listEnd The last delimiter to use (defaults to $delimiter)
109+
* @return string
110+
*/
111+
protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false) {
112+
if ($listEnd === false || !isset($elements[1])) {
113+
return implode(', ', $elements);
114+
}
115+
$lastElement = array_slice($elements, -1);
116+
$firsElements = join(', ', array_slice($elements, 0, -1));
117+
$implodedElements = array_merge(array($firsElements), $lastElement);
118+
return join(" $listEnd ", $implodedElements);
119+
}
120+
121+
/**
122+
* Validates the given $type, if there's an associated self::$wording. If not, throws an
123+
* exception.
124+
*
125+
* @param string $type The type to validate
126+
*
127+
* @throws StandardUnexpectedValueException
128+
*/
129+
protected function validateTypeNameWording( $type) {
130+
if (!isset(self::$wording[$type])) {
131+
throw new StandardUnexpectedValueException(
132+
sprintf(
133+
"No wording for %s available, expected wordings are: [%s]",
134+
var_export($type, true),
135+
implode(', ', array_filter(self::$wording)))
136+
);
86137
}
87138
}
88139

@@ -126,7 +177,7 @@ protected function validateType($value, $type)
126177
if ('string' === $type) {
127178
return is_string($value);
128179
}
129-
180+
130181
if ('email' === $type) {
131182
return is_string($value);
132183
}

tests/Constraints/TypeTest.php

+11-8
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ class TypeTest extends \PHPUnit_Framework_TestCase
2626
public function provideIndefiniteArticlesForTypes()
2727
{
2828
return array(
29-
array('integer', 'an',),
30-
array('number', 'a',),
31-
array('boolean', 'a',),
32-
array('object', 'an',),
33-
array('array', 'an',),
34-
array('string', 'a',),
35-
array('null', 'a', array(), 'array',),
29+
array('integer', 'an integer',),
30+
array('number', 'a number',),
31+
array('boolean', 'a boolean',),
32+
array('object', 'an object',),
33+
array('array', 'an array',),
34+
array('string', 'a string',),
35+
array('null', 'a null', array(), 'array',),
36+
array(array('string', 'boolean', 'integer'), 'a string, a boolean or an integer',),
37+
array(array('string', 'boolean'), 'a string or a boolean',),
38+
array(array('string'), 'a string',),
3639
);
3740
}
3841

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

4952
/**

0 commit comments

Comments
 (0)