mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-06 07:49:24 +03:00
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:
parent
949b853678
commit
17520876d8
@ -13,7 +13,7 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
|
||||
/**
|
||||
* Note: fragment variable definitions are experimental and may be changed
|
||||
* or removed in the future.
|
||||
*
|
||||
*
|
||||
* @var VariableDefinitionNode[]|NodeList
|
||||
*/
|
||||
public $variableDefinitions;
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
$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);
|
||||
$argDef = $context->getArgument();
|
||||
if (!$argDef) {
|
||||
$argumentOf = $ancestors[count($ancestors) - 1];
|
||||
if ($argumentOf->kind === NodeKind::FIELD) {
|
||||
$fieldDef = $context->getFieldDef();
|
||||
$parentType = $context->getParentType();
|
||||
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) {
|
||||
} else if ($argumentOf->kind === NodeKind::DIRECTIVE) {
|
||||
$directive = $context->getDirective();
|
||||
if ($directive) {
|
||||
$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]
|
||||
));
|
||||
}
|
||||
|
@ -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])
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
65
tests/Utils/QuotedOrListTest.php
Normal file
65
tests/Utils/QuotedOrListTest.php
Normal 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'])
|
||||
);
|
||||
}
|
||||
}
|
45
tests/Utils/SuggestionListTest.php
Normal file
45
tests/Utils/SuggestionListTest.php
Normal 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']
|
||||
);
|
||||
}
|
||||
}
|
@ -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)]
|
||||
);
|
||||
}
|
||||
|
@ -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)]
|
||||
);
|
||||
}
|
||||
|
@ -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)]
|
||||
);
|
||||
}
|
||||
|
@ -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]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user