Update some validators to latest upstream version

This includes:
graphql/graphql-js#1147
graphql/graphql-js#355

This also fixes two bugs in the Schema
 - types that were not found where still added to the typeMap
 - InputObject args should not be searched for types.
This commit is contained in:
Daniel Tschinder 2018-02-15 17:19:53 +01:00
parent 949b853678
commit 17520876d8
13 changed files with 485 additions and 106 deletions

View File

@ -224,7 +224,11 @@ class Schema
public function getType($name)
{
if (!isset($this->resolvedTypes[$name])) {
$this->resolvedTypes[$name] = $this->loadType($name);
$type = $this->loadType($name);
if (!$type) {
return null;
}
$this->resolvedTypes[$name] = $type;
}
return $this->resolvedTypes[$name];
}

View File

@ -122,7 +122,7 @@ class TypeInfo
if ($type instanceof ObjectType) {
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
}
if ($type instanceof ObjectType || $type instanceof InterfaceType || $type instanceof InputObjectType) {
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
foreach ((array) $type->getFields() as $fieldName => $field) {
if (!empty($field->args)) {
$fieldArgTypes = array_map(function(FieldArgument $arg) { return $arg->getType(); }, $field->args);
@ -131,6 +131,11 @@ class TypeInfo
$nestedTypes[] = $field->getType();
}
}
if ($type instanceof InputObjectType) {
foreach ((array) $type->getFields() as $fieldName => $field) {
$nestedTypes[] = $field->getType();
}
}
foreach ($nestedTypes as $type) {
$typeMap = self::extractTypes($type, $typeMap);
}

View File

@ -471,4 +471,65 @@ class Utils
}
};
}
/**
* @param string[] $items
* @return string
*/
public static function quotedOrList(array $items)
{
$items = array_map(function($item) { return "\"$item\""; }, $items);
return self::orList($items);
}
public static function orList(array $items)
{
if (!$items) {
throw new \LogicException('items must not need to be empty.');
}
$selected = array_slice($items, 0, 5);
$selectedLength = count($selected);
$firstSelected = $selected[0];
if ($selectedLength === 1) {
return $firstSelected;
}
return array_reduce(
range(1, $selectedLength - 1),
function ($list, $index) use ($selected, $selectedLength) {
return $list.
($selectedLength > 2 && $index !== $selectedLength - 1? ', ' : ' ') .
($index === $selectedLength - 1 ? 'or ' : '') .
$selected[$index];
},
$firstSelected
);
}
/**
* Given an invalid input string and a list of valid options, returns a filtered
* list of valid options sorted based on their similarity with the input.
*
* @param string $input
* @param array $options
* @return string[]
*/
public static function suggestionList($input, array $options)
{
$optionsByDistance = [];
$inputThreshold = mb_strlen($input) / 2;
foreach ($options as $option) {
$distance = levenshtein($input, $option);
$threshold = max($inputThreshold, mb_strlen($option) / 2, 1);
if ($distance <= $threshold) {
$optionsByDistance[$option] = $distance;
}
}
asort($optionsByDistance);
return array_keys($optionsByDistance);
}
}

View File

@ -4,27 +4,27 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
class FieldsOnCorrectType extends AbstractValidationRule
{
static function undefinedFieldMessage($field, $type, array $suggestedTypes = [])
static function undefinedFieldMessage($fieldName, $type, array $suggestedTypeNames, array $suggestedFieldNames)
{
$message = 'Cannot query field "' . $field . '" on type "' . $type.'".';
$message = 'Cannot query field "' . $fieldName . '" on type "' . $type.'".';
$maxLength = 5;
$count = count($suggestedTypes);
if ($count > 0) {
$suggestions = array_slice($suggestedTypes, 0, $maxLength);
$suggestions = Utils::map($suggestions, function($t) { return "\"$t\""; });
$suggestions = implode(', ', $suggestions);
if ($count > $maxLength) {
$suggestions .= ', and ' . ($count - $maxLength) . ' other types';
}
$message .= " However, this field exists on $suggestions.";
$message .= ' Perhaps you meant to use an inline fragment?';
if ($suggestedTypeNames) {
$suggestions = Utils::quotedOrList($suggestedTypeNames);
$message .= " Did you mean to use an inline fragment on $suggestions?";
} else if ($suggestedFieldNames) {
$suggestions = Utils::quotedOrList($suggestedFieldNames);
$message .= " Did you mean {$suggestions}?";
}
return $message;
}
@ -37,8 +37,32 @@ class FieldsOnCorrectType extends AbstractValidationRule
if ($type) {
$fieldDef = $context->getFieldDef();
if (!$fieldDef) {
// This isn't valid. Let's find suggestions, if any.
$schema = $context->getSchema();
$fieldName = $node->name->value;
// First determine if there are any suggested types to condition on.
$suggestedTypeNames = $this->getSuggestedTypeNames(
$schema,
$type,
$fieldName
);
// If there are no suggested types, then perhaps this was a typo?
$suggestedFieldNames = $suggestedTypeNames
? []
: $this->getSuggestedFieldNames(
$schema,
$type,
$fieldName
);
// Report an error, including helpful suggestions.
$context->reportError(new Error(
static::undefinedFieldMessage($node->name->value, $type->name),
static::undefinedFieldMessage(
$node->name->value,
$type->name,
$suggestedTypeNames,
$suggestedFieldNames
),
[$node]
));
}
@ -46,4 +70,72 @@ class FieldsOnCorrectType extends AbstractValidationRule
}
];
}
/**
* Go through all of the implementations of type, as well as the interfaces
* that they implement. If any of those types include the provided field,
* suggest them, sorted by how often the type is referenced, starting
* with Interfaces.
*
* @param Schema $schema
* @param $type
* @param string $fieldName
* @return array
*/
private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
{
if (Type::isAbstractType($type)) {
$suggestedObjectTypes = [];
$interfaceUsageCount = [];
foreach($schema->getPossibleTypes($type) as $possibleType) {
$fields = $possibleType->getFields();
if (!isset($fields[$fieldName])) {
continue;
}
// This object type defines this field.
$suggestedObjectTypes[] = $possibleType->name;
foreach($possibleType->getInterfaces() as $possibleInterface) {
$fields = $possibleInterface->getFields();
if (!isset($fields[$fieldName])) {
continue;
}
// This interface type defines this field.
$interfaceUsageCount[$possibleInterface->name] =
!isset($interfaceUsageCount[$possibleInterface->name])
? 0
: $interfaceUsageCount[$possibleInterface->name] + 1;
}
}
// Suggest interface types based on how common they are.
arsort($interfaceUsageCount);
$suggestedInterfaceTypes = array_keys($interfaceUsageCount);
// Suggest both interface and object types.
return array_merge($suggestedInterfaceTypes, $suggestedObjectTypes);
}
// Otherwise, must be an Object type, which does not have possible fields.
return [];
}
/**
* For the field name provided, determine if there are any similar field names
* that may be the result of a typo.
*
* @param Schema $schema
* @param $type
* @param string $fieldName
* @return array|string[]
*/
private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
{
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
$possibleFieldNames = array_keys($type->getFields());
return Utils::suggestionList($fieldName, $possibleFieldNames);
}
// Otherwise, must be a Union type, which does not define fields.
return [];
}
}

View File

@ -7,56 +7,68 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
/**
* Known argument names
*
* A GraphQL field is only valid if all supplied arguments are defined by
* that field.
*/
class KnownArgumentNames extends AbstractValidationRule
{
public static function unknownArgMessage($argName, $fieldName, $type)
public static function unknownArgMessage($argName, $fieldName, $typeName, array $suggestedArgs)
{
return "Unknown argument \"$argName\" on field \"$fieldName\" of type \"$type\".";
$message = "Unknown argument \"$argName\" on field \"$fieldName\" of type \"$typeName\".";
if ($suggestedArgs) {
$message .= ' Did you mean ' . Utils::quotedOrList($suggestedArgs) . '?';
}
return $message;
}
public static function unknownDirectiveArgMessage($argName, $directiveName)
public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs)
{
return "Unknown argument \"$argName\" on directive \"@$directiveName\".";
$message = "Unknown argument \"$argName\" on directive \"@$directiveName\".";
if ($suggestedArgs) {
$message .= ' Did you mean ' . Utils::quotedOrList($suggestedArgs) . '?';
}
return $message;
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::ARGUMENT => function(ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
$argDef = $context->getArgument();
if (!$argDef) {
$argumentOf = $ancestors[count($ancestors) - 1];
if ($argumentOf->kind === NodeKind::FIELD) {
$fieldDef = $context->getFieldDef();
if ($fieldDef) {
$fieldArgDef = null;
foreach ($fieldDef->args as $arg) {
if ($arg->name === $node->name->value) {
$fieldArgDef = $arg;
break;
}
}
if (!$fieldArgDef) {
$parentType = $context->getParentType();
Utils::invariant($parentType);
if ($fieldDef && $parentType) {
$context->reportError(new Error(
self::unknownArgMessage($node->name->value, $fieldDef->name, $parentType->name),
self::unknownArgMessage(
$node->name->value,
$fieldDef->name,
$parentType->name,
Utils::suggestionList(
$node->name->value,
array_map(function ($arg) { return $arg->name; }, $fieldDef->args)
)
),
[$node]
));
}
}
} else if ($argumentOf->kind === NodeKind::DIRECTIVE) {
$directive = $context->getDirective();
if ($directive) {
$directiveArgDef = null;
foreach ($directive->args as $arg) {
if ($arg->name === $node->name->value) {
$directiveArgDef = $arg;
break;
}
}
if (!$directiveArgDef) {
$context->reportError(new Error(
self::unknownDirectiveArgMessage($node->name->value, $directive->name),
self::unknownDirectiveArgMessage(
$node->name->value,
$directive->name,
Utils::suggestionList(
$node->name->value,
array_map(function ($arg) { return $arg->name; }, $directive->args)
)
),
[$node]
));
}

View File

@ -1,35 +1,55 @@
<?php
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
/**
* Known type names
*
* A GraphQL document is only valid if referenced types (specifically
* variable definitions and fragment conditions) are defined by the type schema.
*/
class KnownTypeNames extends AbstractValidationRule
{
static function unknownTypeMessage($type)
static function unknownTypeMessage($type, array $suggestedTypes)
{
return "Unknown type \"$type\".";
$message = "Unknown type \"$type\".";
if ($suggestedTypes) {
$suggestions = Utils::quotedOrList($suggestedTypes);
$message .= " Did you mean $suggestions?";
}
return $message;
}
public function getVisitor(ValidationContext $context)
{
$skip = function() {return Visitor::skipNode();};
$skip = function() { return Visitor::skipNode(); };
return [
// TODO: when validating IDL, re-enable these. Experimental version does not
// add unreferenced types, resulting in false-positive errors. Squelched
// errors for now.
NodeKind::OBJECT_TYPE_DEFINITION => $skip,
NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
NodeKind::UNION_TYPE_DEFINITION => $skip,
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip,
NodeKind::NAMED_TYPE => function(NamedTypeNode $node, $key) use ($context) {
NodeKind::NAMED_TYPE => function(NamedTypeNode $node) use ($context) {
$schema = $context->getSchema();
$typeName = $node->name->value;
$type = $context->getSchema()->getType($typeName);
$type = $schema->getType($typeName);
if (!$type) {
$context->reportError(new Error(self::unknownTypeMessage($typeName), [$node]));
$context->reportError(new Error(
self::unknownTypeMessage(
$typeName,
Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
), [$node])
);
}
}
];

View File

@ -0,0 +1,65 @@
<?php
namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
class QuotedOrListTest extends \PHPUnit_Framework_TestCase
{
// DESCRIBE: quotedOrList
/**
* @it Does not accept an empty list
*/
public function testResturnsResultsWhenInputIsEmpty()
{
$this->setExpectedException(\LogicException::class);
Utils::quotedOrList([]);
}
/**
* @it Returns single quoted item
*/
public function testReturnsSingleQuotedItem()
{
$this->assertEquals(
'"A"',
Utils::quotedOrList(['A'])
);
}
/**
* @it Returns two item list
*/
public function testReturnsTwoItemList()
{
$this->assertEquals(
'"A" or "B"',
Utils::quotedOrList(['A', 'B'])
);
}
/**
* @it Returns comma separated many item list
*/
public function testReturnsCommaSeparatedManyItemList()
{
$this->assertEquals(
'"A", "B" or "C"',
Utils::quotedOrList(['A', 'B', 'C'])
);
}
/**
* @it Limits to five items
*/
public function testLimitsToFiveItems()
{
$this->assertEquals(
'"A", "B", "C", "D" or "E"',
Utils::quotedOrList(['A', 'B', 'C', 'D', 'E', 'F'])
);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
class SuggestionListTest extends \PHPUnit_Framework_TestCase
{
// DESCRIBE: suggestionList
/**
* @it Returns results when input is empty
*/
public function testResturnsResultsWhenInputIsEmpty()
{
$this->assertEquals(
Utils::suggestionList('', ['a']),
['a']
);
}
/**
* @it Returns empty array when there are no options
*/
public function testReturnsEmptyArrayWhenThereAreNoOptions()
{
$this->assertEquals(
Utils::suggestionList('input', []),
[]
);
}
/**
* @it Returns options sorted based on similarity
*/
public function testReturnsOptionsSortedBasedOnSimilarity()
{
$this->assertEquals(
Utils::suggestionList('abc', ['a', 'ab', 'abc']),
['abc', 'ab']
);
}
}

View File

@ -97,8 +97,10 @@ class FieldsOnCorrectTypeTest extends TestCase
}
}
}',
[ $this->undefinedField('unknown_pet_field', 'Pet', [], 3, 9),
$this->undefinedField('unknown_cat_field', 'Cat', [], 5, 13) ]
[
$this->undefinedField('unknown_pet_field', 'Pet', [], [], 3, 9),
$this->undefinedField('unknown_cat_field', 'Cat', [], [], 5, 13)
]
);
}
@ -111,7 +113,7 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment fieldNotDefined on Dog {
meowVolume
}',
[$this->undefinedField('meowVolume', 'Dog', [], 3, 9)]
[$this->undefinedField('meowVolume', 'Dog', [], ['barkVolume'], 3, 9)]
);
}
@ -126,7 +128,7 @@ class FieldsOnCorrectTypeTest extends TestCase
deeper_unknown_field
}
}',
[$this->undefinedField('unknown_field', 'Dog', [], 3, 9)]
[$this->undefinedField('unknown_field', 'Dog', [], [], 3, 9)]
);
}
@ -141,7 +143,7 @@ class FieldsOnCorrectTypeTest extends TestCase
unknown_field
}
}',
[$this->undefinedField('unknown_field', 'Pet', [], 4, 11)]
[$this->undefinedField('unknown_field', 'Pet', [], [], 4, 11)]
);
}
@ -156,7 +158,7 @@ class FieldsOnCorrectTypeTest extends TestCase
meowVolume
}
}',
[$this->undefinedField('meowVolume', 'Dog', [], 4, 11)]
[$this->undefinedField('meowVolume', 'Dog', [], ['barkVolume'], 4, 11)]
);
}
@ -169,7 +171,7 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment aliasedFieldTargetNotDefined on Dog {
volume : mooVolume
}',
[$this->undefinedField('mooVolume', 'Dog', [], 3, 9)]
[$this->undefinedField('mooVolume', 'Dog', [], ['barkVolume'], 3, 9)]
);
}
@ -182,7 +184,7 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment aliasedLyingFieldTargetNotDefined on Dog {
barkVolume : kawVolume
}',
[$this->undefinedField('kawVolume', 'Dog', [], 3, 9)]
[$this->undefinedField('kawVolume', 'Dog', [], ['barkVolume'], 3, 9)]
);
}
@ -195,7 +197,7 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment notDefinedOnInterface on Pet {
tailLength
}',
[$this->undefinedField('tailLength', 'Pet', [], 3, 9)]
[$this->undefinedField('tailLength', 'Pet', [], [], 3, 9)]
);
}
@ -208,8 +210,7 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment definedOnImplementorsButNotInterface on Pet {
nickname
}',
//[$this->undefinedField('nickname', 'Pet', [ 'Cat', 'Dog' ], 3, 9)]
[$this->undefinedField('nickname', 'Pet', [ ], 3, 9)]
[$this->undefinedField('nickname', 'Pet', ['Dog', 'Cat'], ['name'], 3, 9)]
);
}
@ -234,7 +235,7 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment directFieldSelectionOnUnion on CatOrDog {
directField
}',
[$this->undefinedField('directField', 'CatOrDog', [], 3, 9)]
[$this->undefinedField('directField', 'CatOrDog', [], [], 3, 9)]
);
}
@ -247,8 +248,14 @@ class FieldsOnCorrectTypeTest extends TestCase
fragment definedOnImplementorsQueriedOnUnion on CatOrDog {
name
}',
//[$this->undefinedField('name', 'CatOrDog', [ 'Being', 'Pet', 'Canine', 'Cat', 'Dog' ], 3, 9)]
[$this->undefinedField('name', 'CatOrDog', [ ], 3, 9)]
[$this->undefinedField(
'name',
'CatOrDog',
['Being', 'Pet', 'Canine', 'Dog', 'Cat'],
[],
3,
9
)]
);
}
@ -273,38 +280,78 @@ class FieldsOnCorrectTypeTest extends TestCase
*/
public function testWorksWithNoSuggestions()
{
$this->assertEquals('Cannot query field "T" on type "f".', FieldsOnCorrectType::undefinedFieldMessage('T', 'f', []));
$this->assertEquals('Cannot query field "f" on type "T".', FieldsOnCorrectType::undefinedFieldMessage('f', 'T', [], []));
}
/**
* @it Works with no small numbers of suggestions
* @it Works with no small numbers of type suggestions
*/
public function testWorksWithNoSmallNumbersOfSuggestions()
public function testWorksWithNoSmallNumbersOfTypeSuggestions()
{
$expected = 'Cannot query field "T" on type "f". ' .
'However, this field exists on "A", "B". ' .
'Perhaps you meant to use an inline fragment?';
$expected = 'Cannot query field "f" on type "T". ' .
'Did you mean to use an inline fragment on "A" or "B"?';
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('T', 'f', [ 'A', 'B' ]));
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('f', 'T', ['A', 'B'], []));
}
/**
* @it Works with lots of suggestions
* @it Works with no small numbers of field suggestions
*/
public function testWorksWithLotsOfSuggestions()
public function testWorksWithNoSmallNumbersOfFieldSuggestions()
{
$expected = 'Cannot query field "T" on type "f". ' .
'However, this field exists on "A", "B", "C", "D", "E", ' .
'and 1 other types. ' .
'Perhaps you meant to use an inline fragment?';
$expected = 'Cannot query field "f" on type "T". ' .
'Did you mean "z" or "y"?';
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('T', 'f', [ 'A', 'B', 'C', 'D', 'E', 'F' ]));
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('f', 'T', [], ['z', 'y']));
}
private function undefinedField($field, $type, $suggestions, $line, $column)
/**
* @it Only shows one set of suggestions at a time, preferring types
*/
public function testOnlyShowsOneSetOfSuggestionsAtATimePreferringTypes()
{
$expected = 'Cannot query field "f" on type "T". ' .
'Did you mean to use an inline fragment on "A" or "B"?';
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('f', 'T', ['A', 'B'], ['z', 'y']));
}
/**
* @it Limits lots of type suggestions
*/
public function testLimitsLotsOfTypeSuggestions()
{
$expected = 'Cannot query field "f" on type "T". ' .
'Did you mean to use an inline fragment on "A", "B", "C", "D" or "E"?';
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage(
'f',
'T',
['A', 'B', 'C', 'D', 'E', 'F'],
[]
));
}
/**
* @it Limits lots of field suggestions
*/
public function testLimitsLotsOfFieldSuggestions()
{
$expected = 'Cannot query field "f" on type "T". ' .
'Did you mean "z", "y", "x", "w" or "v"?';
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage(
'f',
'T',
[],
['z', 'y', 'x', 'w', 'v', 'u']
));
}
private function undefinedField($field, $type, $suggestedTypes, $suggestedFields, $line, $column)
{
return FormattedError::create(
FieldsOnCorrectType::undefinedFieldMessage($field, $type, $suggestions),
FieldsOnCorrectType::undefinedFieldMessage($field, $type, $suggestedTypes, $suggestedFields),
[new SourceLocation($line, $column)]
);
}

View File

@ -112,7 +112,21 @@ class KnownArgumentNamesTest extends TestCase
dog @skip(unless: true)
}
', [
$this->unknownDirectiveArg('unless', 'skip', 3, 19),
$this->unknownDirectiveArg('unless', 'skip', [], 3, 19),
]);
}
/**
* @it misspelled directive args are reported
*/
public function testMisspelledDirectiveArgsAreReported()
{
$this->expectFailsRule(new KnownArgumentNames, '
{
dog @skip(iff: true)
}
', [
$this->unknownDirectiveArg('iff', 'skip', ['if'], 3, 19),
]);
}
@ -126,7 +140,21 @@ class KnownArgumentNamesTest extends TestCase
doesKnowCommand(unknown: true)
}
', [
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', 3, 25),
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', [],3, 25),
]);
}
/**
* @it misspelled arg name is reported
*/
public function testMisspelledArgNameIsReported()
{
$this->expectFailsRule(new KnownArgumentNames, '
fragment invalidArgName on Dog {
doesKnowCommand(dogcommand: true)
}
', [
$this->unknownArg('dogcommand', 'doesKnowCommand', 'Dog', ['dogCommand'],3, 25),
]);
}
@ -140,8 +168,8 @@ class KnownArgumentNamesTest extends TestCase
doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true)
}
', [
$this->unknownArg('whoknows', 'doesKnowCommand', 'Dog', 3, 25),
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', 3, 55),
$this->unknownArg('whoknows', 'doesKnowCommand', 'Dog', [], 3, 25),
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', [], 3, 55),
]);
}
@ -164,23 +192,23 @@ class KnownArgumentNamesTest extends TestCase
}
}
', [
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', 4, 27),
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', 9, 31),
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', [], 4, 27),
$this->unknownArg('unknown', 'doesKnowCommand', 'Dog', [], 9, 31),
]);
}
private function unknownArg($argName, $fieldName, $typeName, $line, $column)
private function unknownArg($argName, $fieldName, $typeName, $suggestedArgs, $line, $column)
{
return FormattedError::create(
KnownArgumentNames::unknownArgMessage($argName, $fieldName, $typeName),
KnownArgumentNames::unknownArgMessage($argName, $fieldName, $typeName, $suggestedArgs),
[new SourceLocation($line, $column)]
);
}
private function unknownDirectiveArg($argName, $directiveName, $line, $column)
private function unknownDirectiveArg($argName, $directiveName, $suggestedArgs, $line, $column)
{
return FormattedError::create(
KnownArgumentNames::unknownDirectiveArgMessage($argName, $directiveName),
KnownArgumentNames::unknownDirectiveArgMessage($argName, $directiveName, $suggestedArgs),
[new SourceLocation($line, $column)]
);
}

View File

@ -42,9 +42,9 @@ class KnownTypeNamesTest extends TestCase
name
}
', [
$this->unknownType('JumbledUpLetters', 2, 23),
$this->unknownType('Badger', 5, 25),
$this->unknownType('Peettt', 8, 29)
$this->unknownType('JumbledUpLetters', [], 2, 23),
$this->unknownType('Badger', [], 5, 25),
$this->unknownType('Peettt', ['Pet'], 8, 29)
]);
}
@ -70,14 +70,14 @@ class KnownTypeNamesTest extends TestCase
}
}
', [
$this->unknownType('NotInTheSchema', 12, 23),
$this->unknownType('NotInTheSchema', [], 12, 23),
]);
}
private function unknownType($typeName, $line, $column)
private function unknownType($typeName, $suggestedTypes, $line, $column)
{
return FormattedError::create(
KnownTypeNames::unknownTypeMessage($typeName),
KnownTypeNames::unknownTypeMessage($typeName, $suggestedTypes),
[new SourceLocation($line, $column)]
);
}

View File

@ -58,7 +58,7 @@ Expected type \"Invalid\", found \"bad value\"; Invalid scalar is always invalid
$query = '{invalid}';
$expectedError = [
'message' => 'Cannot query field "invalid" on type "QueryRoot".',
'message' => 'Cannot query field "invalid" on type "QueryRoot". Did you mean "invalidArg"?',
'locations' => [ ['line' => 1, 'column' => 2] ]
];
$this->expectFailsCompleteValidation($query, [$expectedError]);