Better error messages for config validation

This commit is contained in:
vladar 2015-08-30 13:41:41 +06:00
parent ee2ce29899
commit 5241c8a5d3
3 changed files with 22 additions and 15 deletions

View File

@ -41,7 +41,8 @@ class Config
public static function validate(array $config, array $definition) public static function validate(array $config, array $definition)
{ {
if (self::$enableValidation) { if (self::$enableValidation) {
self::_validateMap($config, $definition); $name = isset($config['name']) ? $config['name'] : 'UnnamedType';
self::_validateMap($name, $config, $definition);
} }
} }
@ -73,29 +74,29 @@ class Config
return $tmp; return $tmp;
} }
private static function _validateMap(array $map, array $definitions, $pathStr = null) private static function _validateMap($typeName, array $map, array $definitions, $pathStr = null)
{ {
$suffix = $pathStr ? " at $pathStr" : ''; $suffix = $pathStr ? " at $pathStr" : '';
// Make sure there are no unexpected keys in map // Make sure there are no unexpected keys in map
$unexpectedKeys = array_keys(array_diff_key($map, $definitions)); $unexpectedKeys = array_keys(array_diff_key($map, $definitions));
Utils::invariant(empty($unexpectedKeys), 'Unexpected keys "%s" ' . $suffix, implode(', ', $unexpectedKeys)); Utils::invariant(empty($unexpectedKeys), 'Error in "'.$typeName.'" type definition: Unexpected keys "%s" ' . $suffix, implode(', ', $unexpectedKeys));
// Make sure that all required keys are present in map // Make sure that all required keys are present in map
$requiredKeys = array_filter($definitions, function($def) {return (self::_getFlags($def) & self::REQUIRED) > 0;}); $requiredKeys = array_filter($definitions, function($def) {return (self::_getFlags($def) & self::REQUIRED) > 0;});
$missingKeys = array_keys(array_diff_key($requiredKeys, $map)); $missingKeys = array_keys(array_diff_key($requiredKeys, $map));
Utils::invariant(empty($missingKeys), 'Required keys missing: "%s"' . $suffix, implode(', ', $missingKeys)); Utils::invariant(empty($missingKeys), 'Error in "'.$typeName.'" type definition: Required keys missing: "%s"' . $suffix, implode(', ', $missingKeys));
// Make sure that every map value is valid given the definition // Make sure that every map value is valid given the definition
foreach ($map as $key => $value) { foreach ($map as $key => $value) {
self::_validateEntry($key, $value, $definitions[$key], $pathStr ? "$pathStr:$key" : $key); self::_validateEntry($typeName, $key, $value, $definitions[$key], $pathStr ? "$pathStr:$key" : $key);
} }
} }
private static function _validateEntry($key, $value, $def, $pathStr) private static function _validateEntry($typeName, $key, $value, $def, $pathStr)
{ {
$type = Utils::getVariableType($value); $type = Utils::getVariableType($value);
$err = 'Expecting %s at "' . $pathStr . '", but got "' . $type . '"'; $err = 'Error in "'.$typeName.'" type definition: expecting %s at "' . $pathStr . '", but got "' . $type . '"';
if ($def instanceof \stdClass) { if ($def instanceof \stdClass) {
if ($def->flags & self::REQUIRED === 0 && $value === null) { if ($def->flags & self::REQUIRED === 0 && $value === null) {
@ -107,14 +108,14 @@ class Config
if ($def->flags & self::KEY_AS_NAME) { if ($def->flags & self::KEY_AS_NAME) {
$value += ['name' => $key]; $value += ['name' => $key];
} }
self::_validateMap($value, $def->definition, $pathStr); self::_validateMap($typeName, $value, $def->definition, $pathStr);
} else if (!empty($def->isArray)) { } else if (!empty($def->isArray)) {
if ($def->flags & self::REQUIRED) { if ($def->flags & self::REQUIRED) {
Utils::invariant(!empty($value), "Value at '$pathStr' cannot be empty array"); Utils::invariant(!empty($value), 'Error in "'.$typeName.'" type definition: ' . "Value at '$pathStr' cannot be empty array");
} }
$err = "Each entry at '$pathStr' must be an array, but '%s' is '%s'"; $err = 'Error in "'.$typeName.'" type definition:' . "Each entry at '$pathStr' must be an array, but '%s' is '%s'";
foreach ($value as $arrKey => $arrValue) { foreach ($value as $arrKey => $arrValue) {
if (is_array($def->definition)) { if (is_array($def->definition)) {
@ -123,21 +124,25 @@ class Config
if ($def->flags & self::KEY_AS_NAME) { if ($def->flags & self::KEY_AS_NAME) {
$arrValue += ['name' => $arrKey]; $arrValue += ['name' => $arrKey];
} }
self::_validateMap($arrValue, $def->definition, "$pathStr:$arrKey"); self::_validateMap($typeName, $arrValue, $def->definition, "$pathStr:$arrKey");
} else { } else {
self::_validateEntry($arrKey, $arrValue, $def->definition, "$pathStr:$arrKey"); self::_validateEntry($typeName, $arrKey, $arrValue, $def->definition, "$pathStr:$arrKey");
} }
} }
} else { } else {
throw new \Exception("Unexpected definition: " . print_r($def, true)); throw new \Exception('Error in "'.$typeName.'" type definition:' . "unexpected definition: " . print_r($def, true));
} }
} else { } else {
Utils::invariant(is_int($def), "Definition for '$pathStr' is expected to be single integer value"); Utils::invariant(is_int($def), 'Error in "'.$typeName.'" type definition:' . "Definition for '$pathStr' is expected to be single integer value");
if ($def & self::REQUIRED) { if ($def & self::REQUIRED) {
Utils::invariant($value !== null, 'Value at "%s" can not be null', $pathStr); Utils::invariant($value !== null, 'Value at "%s" can not be null', $pathStr);
} }
if (null === $value) {
return ; // Allow nulls for non-required fields
}
switch (true) { switch (true) {
case $def & self::ANY: case $def & self::ANY:
break; break;

View File

@ -68,6 +68,8 @@ class ObjectType extends Type implements OutputType, CompositeType
public function __construct(array $config) public function __construct(array $config)
{ {
Utils::invariant(!empty($config['name']), 'Every type is expected to have name');
$this->name = $config['name']; $this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null; $this->description = isset($config['description']) ? $config['description'] : null;
$this->_config = $config; $this->_config = $config;

View File

@ -335,7 +335,7 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->assertSame( $this->assertSame(
'Expecting callable or instance of GraphQL\Type\Definition\ObjectType at "types:0", but got "' . get_class($type) . '"', 'Error in "BadUnion" type definition: expecting callable or instance of GraphQL\Type\Definition\ObjectType at "types:0", but got "' . get_class($type) . '"',
$e->getMessage() $e->getMessage()
); );
} }