This commit is contained in:
Your Name
2021-07-26 19:46:18 +02:00
parent e7a49138bb
commit aae17f10a6
818 changed files with 70695 additions and 16408 deletions
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Type\Schema;
abstract class ASTValidationContext
{
/** @var DocumentNode */
protected $ast;
/** @var Error[] */
protected $errors;
/** @var Schema */
protected $schema;
public function __construct(DocumentNode $ast, ?Schema $schema = null)
{
$this->ast = $ast;
$this->schema = $schema;
$this->errors = [];
}
public function reportError(Error $error)
{
$this->errors[] = $error;
}
/**
* @return Error[]
*/
public function getErrors()
{
return $this->errors;
}
/**
* @return DocumentNode
*/
public function getDocument()
{
return $this->ast;
}
public function getSchema() : ?Schema
{
return $this->schema;
}
}
@@ -28,12 +28,13 @@ use GraphQL\Validator\Rules\NoUnusedFragments;
use GraphQL\Validator\Rules\NoUnusedVariables;
use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged;
use GraphQL\Validator\Rules\PossibleFragmentSpreads;
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
use GraphQL\Validator\Rules\ProvidedRequiredArguments;
use GraphQL\Validator\Rules\ProvidedRequiredArgumentsOnDirectives;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use GraphQL\Validator\Rules\QuerySecurityRule;
use GraphQL\Validator\Rules\ScalarLeafs;
use GraphQL\Validator\Rules\SingleFieldSubscription;
use GraphQL\Validator\Rules\UniqueArgumentNames;
use GraphQL\Validator\Rules\UniqueDirectivesPerLocation;
use GraphQL\Validator\Rules\UniqueFragmentNames;
@@ -43,14 +44,11 @@ use GraphQL\Validator\Rules\UniqueVariableNames;
use GraphQL\Validator\Rules\ValidationRule;
use GraphQL\Validator\Rules\ValuesOfCorrectType;
use GraphQL\Validator\Rules\VariablesAreInputTypes;
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
use Throwable;
use function array_filter;
use function array_map;
use function array_merge;
use function count;
use function implode;
use function is_array;
use function sprintf;
@@ -113,7 +111,7 @@ class DocumentValidator
return [];
}
$typeInfo = $typeInfo ?: new TypeInfo($schema);
$typeInfo = $typeInfo ?? new TypeInfo($schema);
return static::visitUsingRules($schema, $typeInfo, $ast, $rules);
}
@@ -142,6 +140,7 @@ class DocumentValidator
ExecutableDefinitions::class => new ExecutableDefinitions(),
UniqueOperationNames::class => new UniqueOperationNames(),
LoneAnonymousOperation::class => new LoneAnonymousOperation(),
SingleFieldSubscription::class => new SingleFieldSubscription(),
KnownTypeNames::class => new KnownTypeNames(),
FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
VariablesAreInputTypes::class => new VariablesAreInputTypes(),
@@ -160,8 +159,7 @@ class DocumentValidator
KnownArgumentNames::class => new KnownArgumentNames(),
UniqueArgumentNames::class => new UniqueArgumentNames(),
ValuesOfCorrectType::class => new ValuesOfCorrectType(),
ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(),
ProvidedRequiredArguments::class => new ProvidedRequiredArguments(),
VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
UniqueInputFieldNames::class => new UniqueInputFieldNames(),
@@ -268,11 +266,11 @@ class DocumentValidator
return is_array($value)
? count(array_filter(
$value,
static function ($item) {
return $item instanceof Exception || $item instanceof Throwable;
static function ($item) : bool {
return $item instanceof Throwable;
}
)) === count($value)
: ($value instanceof Exception || $value instanceof Throwable);
: $value instanceof Throwable;
}
public static function append(&$arr, $items)
@@ -309,18 +307,55 @@ class DocumentValidator
return $context->getErrors();
}
public static function assertValidSDLExtension(DocumentNode $documentAST, Schema $schema)
/**
* @param ValidationRule[]|null $rules
*
* @return Error[]
*
* @throws Exception
*/
public static function validateSDL(
DocumentNode $documentAST,
?Schema $schemaToExtend = null,
?array $rules = null
) {
$usedRules = $rules ?? self::sdlRules();
$context = new SDLValidationContext($documentAST, $schemaToExtend);
$visitors = [];
foreach ($usedRules as $rule) {
$visitors[] = $rule->getSDLVisitor($context);
}
Visitor::visit($documentAST, Visitor::visitInParallel($visitors));
return $context->getErrors();
}
public static function assertValidSDL(DocumentNode $documentAST)
{
$errors = self::visitUsingRules($schema, new TypeInfo($schema), $documentAST, self::sdlRules());
if (count($errors) !== 0) {
throw new Error(
implode(
"\n\n",
array_map(static function (Error $error) : string {
return $error->message;
}, $errors)
)
);
$errors = self::validateSDL($documentAST);
if (count($errors) > 0) {
throw new Error(self::combineErrorMessages($errors));
}
}
public static function assertValidSDLExtension(DocumentNode $documentAST, Schema $schema)
{
$errors = self::validateSDL($documentAST, $schema);
if (count($errors) > 0) {
throw new Error(self::combineErrorMessages($errors));
}
}
/**
* @param Error[] $errors
*/
private static function combineErrorMessages(array $errors) : string
{
$str = '';
foreach ($errors as $error) {
$str .= ($error->getMessage() . "\n\n");
}
return $str;
}
}
@@ -31,7 +31,7 @@ class DisableIntrospection extends QuerySecurityRule
return $this->invokeIfNeeded(
$context,
[
NodeKind::FIELD => static function (FieldNode $node) use ($context) {
NodeKind::FIELD => static function (FieldNode $node) use ($context) : void {
if ($node->name->value !== '__type' && $node->name->value !== '__schema') {
return;
}
@@ -6,11 +6,11 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\ExecutableDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\TypeSystemDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ValidationContext;
use function sprintf;
@@ -25,12 +25,10 @@ class ExecutableDefinitions extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) {
/** @var Node $definition */
NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) : VisitorOperation {
/** @var ExecutableDefinitionNode|TypeSystemDefinitionNode $definition */
foreach ($node->definitions as $definition) {
if ($definition instanceof OperationDefinitionNode ||
$definition instanceof FragmentDefinitionNode
) {
if ($definition instanceof ExecutableDefinitionNode) {
continue;
}
@@ -16,6 +16,7 @@ use GraphQL\Validator\ValidationContext;
use function array_keys;
use function array_merge;
use function arsort;
use function count;
use function sprintf;
class FieldsOnCorrectType extends ValidationRule
@@ -23,7 +24,7 @@ class FieldsOnCorrectType extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::FIELD => function (FieldNode $node) use ($context) {
NodeKind::FIELD => function (FieldNode $node) use ($context) : void {
$type = $context->getParentType();
if (! $type) {
return;
@@ -84,15 +85,13 @@ class FieldsOnCorrectType extends ValidationRule
$interfaceUsageCount = [];
foreach ($schema->getPossibleTypes($type) as $possibleType) {
$fields = $possibleType->getFields();
if (! isset($fields[$fieldName])) {
if (! $possibleType->hasField($fieldName)) {
continue;
}
// This object type defines this field.
$suggestedObjectTypes[] = $possibleType->name;
foreach ($possibleType->getInterfaces() as $possibleInterface) {
$fields = $possibleInterface->getFields();
if (! isset($fields[$fieldName])) {
if (! $possibleInterface->hasField($fieldName)) {
continue;
}
// This interface type defines this field.
@@ -127,7 +126,7 @@ class FieldsOnCorrectType extends ValidationRule
private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
{
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
$possibleFieldNames = array_keys($type->getFields());
$possibleFieldNames = $type->getFieldNames();
return Utils::suggestionList($fieldName, $possibleFieldNames);
}
@@ -156,7 +155,7 @@ class FieldsOnCorrectType extends ValidationRule
$suggestions = Utils::quotedOrList($suggestedTypeNames);
$message .= sprintf(' Did you mean to use an inline fragment on %s?', $suggestions);
} elseif (! empty($suggestedFieldNames)) {
} elseif (count($suggestedFieldNames) > 0) {
$suggestions = Utils::quotedOrList($suggestedFieldNames);
$message .= sprintf(' Did you mean %s?', $suggestions);
@@ -19,7 +19,7 @@ class FragmentsOnCompositeTypes extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::INLINE_FRAGMENT => static function (InlineFragmentNode $node) use ($context) {
NodeKind::INLINE_FRAGMENT => static function (InlineFragmentNode $node) use ($context) : void {
if (! $node->typeCondition) {
return;
}
@@ -34,7 +34,7 @@ class FragmentsOnCompositeTypes extends ValidationRule
[$node->typeCondition]
));
},
NodeKind::FRAGMENT_DEFINITION => static function (FragmentDefinitionNode $node) use ($context) {
NodeKind::FRAGMENT_DEFINITION => static function (FragmentDefinitionNode $node) use ($context) : void {
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
if (! $type || Type::isCompositeType($type)) {
@@ -6,9 +6,11 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_map;
@@ -25,58 +27,40 @@ class KnownArgumentNames extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::ARGUMENT => static function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
/** @var NodeList|Node[] $ancestors */
$knownArgumentNamesOnDirectives = new KnownArgumentNamesOnDirectives();
return $knownArgumentNamesOnDirectives->getVisitor($context) + [
NodeKind::ARGUMENT => static function (ArgumentNode $node) use ($context) : void {
$argDef = $context->getArgument();
if ($argDef !== null) {
return;
}
$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,
Utils::suggestionList(
$node->name->value,
array_map(
static function ($arg) {
return $arg->name;
},
$fieldDef->args
)
)
),
[$node]
));
}
} elseif ($argumentOf->kind === NodeKind::DIRECTIVE) {
$directive = $context->getDirective();
if ($directive) {
$context->reportError(new Error(
self::unknownDirectiveArgMessage(
$node->name->value,
$directive->name,
Utils::suggestionList(
$node->name->value,
array_map(
static function ($arg) {
return $arg->name;
},
$directive->args
)
)
),
[$node]
));
}
$fieldDef = $context->getFieldDef();
$parentType = $context->getParentType();
if ($fieldDef === null || ! ($parentType instanceof Type)) {
return;
}
$context->reportError(new Error(
self::unknownArgMessage(
$node->name->value,
$fieldDef->name,
$parentType->name,
Utils::suggestionList(
$node->name->value,
array_map(
static function ($arg) : string {
return $arg->name;
},
$fieldDef->args
)
)
),
[$node]
));
return;
},
];
}
@@ -87,20 +71,7 @@ class KnownArgumentNames extends ValidationRule
public static function unknownArgMessage($argName, $fieldName, $typeName, array $suggestedArgs)
{
$message = sprintf('Unknown argument "%s" on field "%s" of type "%s".', $argName, $fieldName, $typeName);
if (! empty($suggestedArgs)) {
$message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs));
}
return $message;
}
/**
* @param string[] $suggestedArgs
*/
public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs)
{
$message = sprintf('Unknown argument "%s" on directive "@%s".', $argName, $directiveName);
if (! empty($suggestedArgs)) {
if (isset($suggestedArgs[0])) {
$message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs));
}
@@ -9,13 +9,17 @@ use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ASTValidationContext;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function array_map;
use function in_array;
use function iterator_to_array;
use function sprintf;
/**
* Known argument names on directives
@@ -25,12 +29,30 @@ use function iterator_to_array;
*/
class KnownArgumentNamesOnDirectives extends ValidationRule
{
protected static function unknownDirectiveArgMessage(string $argName, string $directionName)
/**
* @param string[] $suggestedArgs
*/
public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs)
{
return 'Unknown argument "' . $argName . '" on directive "@' . $directionName . '".';
$message = sprintf('Unknown argument "%s" on directive "@%s".', $argName, $directiveName);
if (isset($suggestedArgs[0])) {
$message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs));
}
return $message;
}
public function getSDLVisitor(SDLValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getVisitor(ValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getASTVisitor(ASTValidationContext $context)
{
$directiveArgs = [];
$schema = $context->getSchema();
@@ -53,27 +75,24 @@ class KnownArgumentNamesOnDirectives extends ValidationRule
$name = $def->name->value;
if ($def->arguments !== null) {
$arguments = $def->arguments;
if ($arguments instanceof NodeList) {
$arguments = iterator_to_array($arguments->getIterator());
}
$directiveArgs[$name] = array_map(static function (InputValueDefinitionNode $arg) : string {
return $arg->name->value;
}, $arguments);
$directiveArgs[$name] = Utils::map(
$def->arguments ?? [],
static function (InputValueDefinitionNode $arg) : string {
return $arg->name->value;
}
);
} else {
$directiveArgs[$name] = [];
}
}
return [
NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($directiveArgs, $context) {
NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($directiveArgs, $context) : VisitorOperation {
$directiveName = $directiveNode->name->value;
$knownArgs = $directiveArgs[$directiveName] ?? null;
if ($directiveNode->arguments === null || ! $knownArgs) {
return;
if ($directiveNode->arguments === null || $knownArgs === null) {
return Visitor::skipNode();
}
foreach ($directiveNode->arguments as $argNode) {
@@ -82,11 +101,14 @@ class KnownArgumentNamesOnDirectives extends ValidationRule
continue;
}
$suggestions = Utils::suggestionList($argName, $knownArgs);
$context->reportError(new Error(
self::unknownDirectiveArgMessage($argName, $directiveName),
self::unknownDirectiveArgMessage($argName, $directiveName, $suggestions),
[$argNode]
));
}
return Visitor::skipNode();
},
];
}
@@ -4,27 +4,67 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Definition\Directive;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ASTValidationContext;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function array_map;
use function count;
use function get_class;
use function in_array;
use function sprintf;
class KnownDirectives extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getSDLVisitor(SDLValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getASTVisitor(ASTValidationContext $context)
{
$locationsMap = [];
$schema = $context->getSchema();
$definedDirectives = $schema->getDirectives();
$definedDirectives = $schema
? $schema->getDirectives()
: Directive::getInternalDirectives();
foreach ($definedDirectives as $directive) {
$locationsMap[$directive->name] = $directive->locations;
@@ -37,11 +77,11 @@ class KnownDirectives extends ValidationRule
continue;
}
$locationsMap[$def->name->value] = array_map(
static function ($name) {
$locationsMap[$def->name->value] = Utils::map(
$def->locations,
static function ($name) : string {
return $name->value;
},
$def->locations
}
);
}
@@ -55,7 +95,7 @@ class KnownDirectives extends ValidationRule
) use (
$context,
$locationsMap
) {
) : void {
$name = $node->name->value;
$locations = $locationsMap[$name] ?? null;
@@ -96,8 +136,8 @@ class KnownDirectives extends ValidationRule
private function getDirectiveLocationForASTPath(array $ancestors)
{
$appliedTo = $ancestors[count($ancestors) - 1];
switch ($appliedTo->kind) {
case NodeKind::OPERATION_DEFINITION:
switch (true) {
case $appliedTo instanceof OperationDefinitionNode:
switch ($appliedTo->operation) {
case 'query':
return DirectiveLocation::QUERY;
@@ -107,46 +147,50 @@ class KnownDirectives extends ValidationRule
return DirectiveLocation::SUBSCRIPTION;
}
break;
case NodeKind::FIELD:
case $appliedTo instanceof FieldNode:
return DirectiveLocation::FIELD;
case NodeKind::FRAGMENT_SPREAD:
case $appliedTo instanceof FragmentSpreadNode:
return DirectiveLocation::FRAGMENT_SPREAD;
case NodeKind::INLINE_FRAGMENT:
case $appliedTo instanceof InlineFragmentNode:
return DirectiveLocation::INLINE_FRAGMENT;
case NodeKind::FRAGMENT_DEFINITION:
case $appliedTo instanceof FragmentDefinitionNode:
return DirectiveLocation::FRAGMENT_DEFINITION;
case NodeKind::SCHEMA_DEFINITION:
case NodeKind::SCHEMA_EXTENSION:
case $appliedTo instanceof VariableDefinitionNode:
return DirectiveLocation::VARIABLE_DEFINITION;
case $appliedTo instanceof SchemaDefinitionNode:
case $appliedTo instanceof SchemaTypeExtensionNode:
return DirectiveLocation::SCHEMA;
case NodeKind::SCALAR_TYPE_DEFINITION:
case NodeKind::SCALAR_TYPE_EXTENSION:
case $appliedTo instanceof ScalarTypeDefinitionNode:
case $appliedTo instanceof ScalarTypeExtensionNode:
return DirectiveLocation::SCALAR;
case NodeKind::OBJECT_TYPE_DEFINITION:
case NodeKind::OBJECT_TYPE_EXTENSION:
case $appliedTo instanceof ObjectTypeDefinitionNode:
case $appliedTo instanceof ObjectTypeExtensionNode:
return DirectiveLocation::OBJECT;
case NodeKind::FIELD_DEFINITION:
case $appliedTo instanceof FieldDefinitionNode:
return DirectiveLocation::FIELD_DEFINITION;
case NodeKind::INTERFACE_TYPE_DEFINITION:
case NodeKind::INTERFACE_TYPE_EXTENSION:
case $appliedTo instanceof InterfaceTypeDefinitionNode:
case $appliedTo instanceof InterfaceTypeExtensionNode:
return DirectiveLocation::IFACE;
case NodeKind::UNION_TYPE_DEFINITION:
case NodeKind::UNION_TYPE_EXTENSION:
case $appliedTo instanceof UnionTypeDefinitionNode:
case $appliedTo instanceof UnionTypeExtensionNode:
return DirectiveLocation::UNION;
case NodeKind::ENUM_TYPE_DEFINITION:
case NodeKind::ENUM_TYPE_EXTENSION:
case $appliedTo instanceof EnumTypeDefinitionNode:
case $appliedTo instanceof EnumTypeExtensionNode:
return DirectiveLocation::ENUM;
case NodeKind::ENUM_VALUE_DEFINITION:
case $appliedTo instanceof EnumValueDefinitionNode:
return DirectiveLocation::ENUM_VALUE;
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
case $appliedTo instanceof InputObjectTypeDefinitionNode:
case $appliedTo instanceof InputObjectTypeExtensionNode:
return DirectiveLocation::INPUT_OBJECT;
case NodeKind::INPUT_VALUE_DEFINITION:
case $appliedTo instanceof InputValueDefinitionNode:
$parentNode = $ancestors[count($ancestors) - 3];
return $parentNode instanceof InputObjectTypeDefinitionNode
? DirectiveLocation::INPUT_FIELD_DEFINITION
: DirectiveLocation::ARGUMENT_DEFINITION;
}
throw new Exception('Unknown directive location: ' . get_class($appliedTo));
}
public static function misplacedDirectiveMessage($directiveName, $location)
@@ -15,7 +15,7 @@ class KnownFragmentNames extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::FRAGMENT_SPREAD => static function (FragmentSpreadNode $node) use ($context) {
NodeKind::FRAGMENT_SPREAD => static function (FragmentSpreadNode $node) use ($context) : void {
$fragmentName = $node->name->value;
$fragment = $context->getFragment($fragmentName);
if ($fragment) {
@@ -8,9 +8,11 @@ use GraphQL\Error\Error;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_keys;
use function count;
use function sprintf;
/**
@@ -23,7 +25,7 @@ class KnownTypeNames extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
$skip = static function () {
$skip = static function () : VisitorOperation {
return Visitor::skipNode();
};
@@ -35,7 +37,7 @@ class KnownTypeNames extends ValidationRule
NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
NodeKind::UNION_TYPE_DEFINITION => $skip,
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip,
NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) use ($context) {
NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) use ($context) : void {
$schema = $context->getSchema();
$typeName = $node->name->value;
$type = $schema->getType($typeName);
@@ -61,7 +63,7 @@ class KnownTypeNames extends ValidationRule
public static function unknownTypeMessage($type, array $suggestedTypes)
{
$message = sprintf('Unknown type "%s".', $type);
if (! empty($suggestedTypes)) {
if (count($suggestedTypes) > 0) {
$suggestions = Utils::quotedOrList($suggestedTypes);
$message .= sprintf(' Did you mean %s?', $suggestions);
@@ -26,11 +26,11 @@ class LoneAnonymousOperation extends ValidationRule
$operationCount = 0;
return [
NodeKind::DOCUMENT => static function (DocumentNode $node) use (&$operationCount) {
NodeKind::DOCUMENT => static function (DocumentNode $node) use (&$operationCount) : void {
$tmp = Utils::filter(
$node->definitions,
static function (Node $definition) {
return $definition->kind === NodeKind::OPERATION_DEFINITION;
static function (Node $definition) : bool {
return $definition instanceof OperationDefinitionNode;
}
);
@@ -39,8 +39,8 @@ class LoneAnonymousOperation extends ValidationRule
NodeKind::OPERATION_DEFINITION => static function (OperationDefinitionNode $node) use (
&$operationCount,
$context
) {
if ($node->name || $operationCount <= 1) {
) : void {
if ($node->name !== null || $operationCount <= 1) {
return;
}
@@ -7,7 +7,7 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Validator\ValidationContext;
use GraphQL\Validator\SDLValidationContext;
/**
* Lone Schema definition
@@ -16,28 +16,40 @@ use GraphQL\Validator\ValidationContext;
*/
class LoneSchemaDefinition extends ValidationRule
{
public function getVisitor(ValidationContext $context)
public static function schemaDefinitionNotAloneMessage()
{
return 'Must provide only one schema definition.';
}
public static function canNotDefineSchemaWithinExtensionMessage()
{
return 'Cannot define a new schema within a schema extension.';
}
public function getSDLVisitor(SDLValidationContext $context)
{
$oldSchema = $context->getSchema();
$alreadyDefined = $oldSchema !== null ? (
$oldSchema->getAstNode() ||
$oldSchema->getQueryType() ||
$oldSchema->getMutationType() ||
$oldSchema->getSubscriptionType()
) : false;
$alreadyDefined = $oldSchema !== null
? (
$oldSchema->getAstNode() !== null ||
$oldSchema->getQueryType() !== null ||
$oldSchema->getMutationType() !== null ||
$oldSchema->getSubscriptionType() !== null
)
: false;
$schemaDefinitionsCount = 0;
return [
NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) {
NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) : void {
if ($alreadyDefined !== false) {
$context->reportError(new Error('Cannot define a new schema within a schema extension.', $node));
$context->reportError(new Error(self::canNotDefineSchemaWithinExtensionMessage(), $node));
return;
}
if ($schemaDefinitionsCount > 0) {
$context->reportError(new Error('Must provide only one schema definition.', $node));
$context->reportError(new Error(self::schemaDefinitionNotAloneMessage(), $node));
}
++$schemaDefinitionsCount;
@@ -9,14 +9,13 @@ use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_merge;
use function array_pop;
use function array_slice;
use function count;
use function implode;
use function is_array;
use function sprintf;
class NoFragmentCycles extends ValidationRule
@@ -43,13 +42,11 @@ class NoFragmentCycles extends ValidationRule
$this->spreadPathIndexByName = [];
return [
NodeKind::OPERATION_DEFINITION => static function () {
NodeKind::OPERATION_DEFINITION => static function () : VisitorOperation {
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
if (! isset($this->visitedFrags[$node->name->value])) {
$this->detectCycleRecursive($node, $context);
}
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) : VisitorOperation {
$this->detectCycleRecursive($node, $context);
return Visitor::skipNode();
},
@@ -58,12 +55,16 @@ class NoFragmentCycles extends ValidationRule
private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
{
if (isset($this->visitedFrags[$fragment->name->value])) {
return;
}
$fragmentName = $fragment->name->value;
$this->visitedFrags[$fragmentName] = true;
$spreadNodes = $context->getFragmentSpreads($fragment);
if (empty($spreadNodes)) {
if (count($spreadNodes) === 0) {
return;
}
@@ -74,38 +75,24 @@ class NoFragmentCycles extends ValidationRule
$spreadName = $spreadNode->name->value;
$cycleIndex = $this->spreadPathIndexByName[$spreadName] ?? null;
$this->spreadPath[] = $spreadNode;
if ($cycleIndex === null) {
$this->spreadPath[] = $spreadNode;
if (empty($this->visitedFrags[$spreadName])) {
$spreadFragment = $context->getFragment($spreadName);
if ($spreadFragment) {
$this->detectCycleRecursive($spreadFragment, $context);
}
$spreadFragment = $context->getFragment($spreadName);
if ($spreadFragment) {
$this->detectCycleRecursive($spreadFragment, $context);
}
array_pop($this->spreadPath);
} else {
$cyclePath = array_slice($this->spreadPath, $cycleIndex);
$nodes = $cyclePath;
if (is_array($spreadNode)) {
$nodes = array_merge($nodes, $spreadNode);
} else {
$nodes[] = $spreadNode;
}
$cyclePath = array_slice($this->spreadPath, $cycleIndex);
$fragmentNames = Utils::map(array_slice($cyclePath, 0, -1), static function ($s) {
return $s->name->value;
});
$context->reportError(new Error(
self::cycleErrorMessage(
$spreadName,
Utils::map(
$cyclePath,
static function ($s) {
return $s->name->value;
}
)
),
$nodes
self::cycleErrorMessage($spreadName, $fragmentNames),
$cyclePath
));
}
array_pop($this->spreadPath);
}
$this->spreadPathIndexByName[$fragmentName] = null;
@@ -119,7 +106,7 @@ class NoFragmentCycles extends ValidationRule
return sprintf(
'Cannot spread fragment "%s" within itself%s.',
$fragName,
! empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : ''
count($spreadNames) > 0 ? ' via ' . implode(', ', $spreadNames) : ''
);
}
}
@@ -23,31 +23,33 @@ class NoUndefinedVariables extends ValidationRule
return [
NodeKind::OPERATION_DEFINITION => [
'enter' => static function () use (&$variableNameDefined) {
'enter' => static function () use (&$variableNameDefined) : void {
$variableNameDefined = [];
},
'leave' => static function (OperationDefinitionNode $operation) use (&$variableNameDefined, $context) {
'leave' => static function (OperationDefinitionNode $operation) use (&$variableNameDefined, $context) : void {
$usages = $context->getRecursiveVariableUsages($operation);
foreach ($usages as $usage) {
$node = $usage['node'];
$varName = $node->name->value;
if (! empty($variableNameDefined[$varName])) {
if ($variableNameDefined[$varName] ?? false) {
continue;
}
$context->reportError(new Error(
self::undefinedVarMessage(
$varName,
$operation->name ? $operation->name->value : null
$operation->name !== null
? $operation->name->value
: null
),
[$node, $operation]
));
}
},
],
NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $def) use (&$variableNameDefined) {
NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $def) use (&$variableNameDefined) : void {
$variableNameDefined[$def->variable->name->value] = true;
},
];
@@ -9,6 +9,7 @@ use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ValidationContext;
use function sprintf;
@@ -26,18 +27,18 @@ class NoUnusedFragments extends ValidationRule
$this->fragmentDefs = [];
return [
NodeKind::OPERATION_DEFINITION => function ($node) {
NodeKind::OPERATION_DEFINITION => function ($node) : VisitorOperation {
$this->operationDefs[] = $node;
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) {
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) : VisitorOperation {
$this->fragmentDefs[] = $def;
return Visitor::skipNode();
},
NodeKind::DOCUMENT => [
'leave' => function () use ($context) {
'leave' => function () use ($context) : void {
$fragmentNameUsed = [];
foreach ($this->operationDefs as $operation) {
@@ -48,7 +49,7 @@ class NoUnusedFragments extends ValidationRule
foreach ($this->fragmentDefs as $fragmentDef) {
$fragName = $fragmentDef->name->value;
if (! empty($fragmentNameUsed[$fragName])) {
if ($fragmentNameUsed[$fragName] ?? false) {
continue;
}
@@ -22,13 +22,15 @@ class NoUnusedVariables extends ValidationRule
return [
NodeKind::OPERATION_DEFINITION => [
'enter' => function () {
'enter' => function () : void {
$this->variableDefs = [];
},
'leave' => function (OperationDefinitionNode $operation) use ($context) {
'leave' => function (OperationDefinitionNode $operation) use ($context) : void {
$variableNameUsed = [];
$usages = $context->getRecursiveVariableUsages($operation);
$opName = $operation->name ? $operation->name->value : null;
$opName = $operation->name !== null
? $operation->name->value
: null;
foreach ($usages as $usage) {
$node = $usage['node'];
@@ -38,7 +40,7 @@ class NoUnusedVariables extends ValidationRule
foreach ($this->variableDefs as $variableDef) {
$variableName = $variableDef->variable->name->value;
if (! empty($variableNameUsed[$variableName])) {
if ($variableNameUsed[$variableName] ?? false) {
continue;
}
@@ -49,7 +51,7 @@ class NoUnusedVariables extends ValidationRule
}
},
],
NodeKind::VARIABLE_DEFINITION => function ($def) {
NodeKind::VARIABLE_DEFINITION => function ($def) : void {
$this->variableDefs[] = $def;
},
];
@@ -19,7 +19,6 @@ use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\PairSet;
use GraphQL\Utils\TypeInfo;
@@ -60,7 +59,7 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
$this->cachedFieldsAndFragmentNames = new SplObjectStorage();
return [
NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) : void {
$conflicts = $this->findConflictsWithinSelectionSet(
$context,
$context->getParentType(),
@@ -251,10 +250,10 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
$fieldName = $selection->name->value;
$fieldDef = null;
if ($parentType instanceof ObjectType ||
$parentType instanceof InterfaceType) {
$tmp = $parentType->getFields();
if (isset($tmp[$fieldName])) {
$fieldDef = $tmp[$fieldName];
$parentType instanceof InterfaceType
) {
if ($parentType->hasField($fieldName)) {
$fieldDef = $parentType->getField($fieldName);
}
}
$responseName = $selection->alias ? $selection->alias->value : $fieldName;
@@ -381,7 +380,7 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
];
}
if (! $this->sameArguments($ast1->arguments ?: [], $ast2->arguments ?: [])) {
if (! $this->sameArguments($ast1->arguments ?? [], $ast2->arguments ?? [])) {
return [
[$responseName, 'they have differing arguments'],
[$ast1],
@@ -467,30 +466,28 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
* Two types conflict if both types could not apply to a value simultaneously.
* Composite types are ignored as their individual field types will be compared
* later recursively. However List and Non-Null types must match.
*
* @return bool
*/
private function doTypesConflict(OutputType $type1, OutputType $type2)
private function doTypesConflict(Type $type1, Type $type2) : bool
{
if ($type1 instanceof ListOfType) {
return $type2 instanceof ListOfType ?
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
true;
return $type2 instanceof ListOfType
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
: true;
}
if ($type2 instanceof ListOfType) {
return $type1 instanceof ListOfType ?
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
true;
return $type1 instanceof ListOfType
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
: true;
}
if ($type1 instanceof NonNull) {
return $type2 instanceof NonNull ?
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
true;
return $type2 instanceof NonNull
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
: true;
}
if ($type2 instanceof NonNull) {
return $type1 instanceof NonNull ?
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
true;
return $type1 instanceof NonNull
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
: true;
}
if (Type::isLeafType($type1) || Type::isLeafType($type2)) {
return $type1 !== $type2;
@@ -848,14 +845,14 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
],
array_reduce(
$conflicts,
static function ($allFields, $conflict) {
static function ($allFields, $conflict) : array {
return array_merge($allFields, $conflict[1]);
},
[$ast1]
),
array_reduce(
$conflicts,
static function ($allFields, $conflict) {
static function ($allFields, $conflict) : array {
return array_merge($allFields, $conflict[2]);
},
[$ast2]
@@ -882,7 +879,7 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
{
if (is_array($reason)) {
$tmp = array_map(
static function ($tmp) {
static function ($tmp) : string {
[$responseName, $subReason] = $tmp;
$reasonMessage = self::reasonMessage($subReason);
@@ -23,7 +23,7 @@ class PossibleFragmentSpreads extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) : void {
$fragType = $context->getType();
$parentType = $context->getParentType();
@@ -38,7 +38,7 @@ class PossibleFragmentSpreads extends ValidationRule
[$node]
));
},
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) : void {
$fragName = $node->name->value;
$fragType = $this->getFragmentType($context, $fragName);
$parentType = $context->getParentType();
@@ -68,12 +68,12 @@ class PossibleFragmentSpreads extends ValidationRule
// Parent type is interface or union, fragment type is object type
if ($parentType instanceof AbstractType && $fragType instanceof ObjectType) {
return $schema->isPossibleType($parentType, $fragType);
return $schema->isSubType($parentType, $fragType);
}
// Parent type is object type, fragment type is interface (or rather rare - union)
if ($parentType instanceof ObjectType && $fragType instanceof AbstractType) {
return $schema->isPossibleType($fragType, $parentType);
return $schema->isSubType($fragType, $parentType);
}
// Both are object types:
@@ -1,98 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class ProvidedNonNullArguments extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::FIELD => [
'leave' => static function (FieldNode $fieldNode) use ($context) {
$fieldDef = $context->getFieldDef();
if (! $fieldDef) {
return Visitor::skipNode();
}
$argNodes = $fieldNode->arguments ?: [];
$argNodeMap = [];
foreach ($argNodes as $argNode) {
$argNodeMap[$argNode->name->value] = $argNodes;
}
foreach ($fieldDef->args as $argDef) {
$argNode = $argNodeMap[$argDef->name] ?? null;
if ($argNode || ! ($argDef->getType() instanceof NonNull)) {
continue;
}
$context->reportError(new Error(
self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
[$fieldNode]
));
}
},
],
NodeKind::DIRECTIVE => [
'leave' => static function (DirectiveNode $directiveNode) use ($context) {
$directiveDef = $context->getDirective();
if (! $directiveDef) {
return Visitor::skipNode();
}
$argNodes = $directiveNode->arguments ?: [];
$argNodeMap = [];
foreach ($argNodes as $argNode) {
$argNodeMap[$argNode->name->value] = $argNodes;
}
foreach ($directiveDef->args as $argDef) {
$argNode = $argNodeMap[$argDef->name] ?? null;
if ($argNode || ! ($argDef->getType() instanceof NonNull)) {
continue;
}
$context->reportError(new Error(
self::missingDirectiveArgMessage(
$directiveNode->name->value,
$argDef->name,
$argDef->getType()
),
[$directiveNode]
));
}
},
],
];
}
public static function missingFieldArgMessage($fieldName, $argName, $type)
{
return sprintf(
'Field "%s" argument "%s" of type "%s" is required but not provided.',
$fieldName,
$argName,
$type
);
}
public static function missingDirectiveArgMessage($directiveName, $argName, $type)
{
return sprintf(
'Directive "@%s" argument "%s" of type "%s" is required but not provided.',
$directiveName,
$argName,
$type
);
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class ProvidedRequiredArguments extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
$providedRequiredArgumentsOnDirectives = new ProvidedRequiredArgumentsOnDirectives();
return $providedRequiredArgumentsOnDirectives->getVisitor($context) + [
NodeKind::FIELD => [
'leave' => static function (FieldNode $fieldNode) use ($context) : ?VisitorOperation {
$fieldDef = $context->getFieldDef();
if (! $fieldDef) {
return Visitor::skipNode();
}
$argNodes = $fieldNode->arguments ?? [];
$argNodeMap = [];
foreach ($argNodes as $argNode) {
$argNodeMap[$argNode->name->value] = $argNode;
}
foreach ($fieldDef->args as $argDef) {
$argNode = $argNodeMap[$argDef->name] ?? null;
if ($argNode || ! $argDef->isRequired()) {
continue;
}
$context->reportError(new Error(
self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
[$fieldNode]
));
}
return null;
},
],
];
}
public static function missingFieldArgMessage($fieldName, $argName, $type)
{
return sprintf(
'Field "%s" argument "%s" of type "%s" is required but not provided.',
$fieldName,
$argName,
$type
);
}
}
@@ -8,18 +8,17 @@ use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ASTValidationContext;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function array_filter;
use function is_array;
use function iterator_to_array;
/**
* Provided required arguments on directives
@@ -29,21 +28,34 @@ use function iterator_to_array;
*/
class ProvidedRequiredArgumentsOnDirectives extends ValidationRule
{
protected static function missingDirectiveArgMessage(string $directiveName, string $argName)
public static function missingDirectiveArgMessage(string $directiveName, string $argName, string $type)
{
return 'Directive "' . $directiveName . '" argument "' . $argName . '" is required but ont provided.';
return 'Directive "@' . $directiveName . '" argument "' . $argName
. '" of type "' . $type . '" is required but not provided.';
}
public function getSDLVisitor(SDLValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getVisitor(ValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getASTVisitor(ASTValidationContext $context)
{
$requiredArgsMap = [];
$schema = $context->getSchema();
$definedDirectives = $schema->getDirectives();
$definedDirectives = $schema
? $schema->getDirectives()
: Directive::getInternalDirectives();
foreach ($definedDirectives as $directive) {
$requiredArgsMap[$directive->name] = Utils::keyMap(
array_filter($directive->args, static function (FieldArgument $arg) : bool {
return $arg->getType() instanceof NonNull && ! isset($arg->defaultValue);
return $arg->isRequired();
}),
static function (FieldArgument $arg) : string {
return $arg->name;
@@ -56,55 +68,61 @@ class ProvidedRequiredArgumentsOnDirectives extends ValidationRule
if (! ($def instanceof DirectiveDefinitionNode)) {
continue;
}
if (is_array($def->arguments)) {
$arguments = $def->arguments;
} elseif ($def->arguments instanceof NodeList) {
$arguments = iterator_to_array($def->arguments->getIterator());
} else {
$arguments = null;
}
$arguments = $def->arguments ?? [];
$requiredArgsMap[$def->name->value] = Utils::keyMap(
$arguments ? array_filter($arguments, static function (Node $argument) : bool {
return $argument instanceof NonNullTypeNode &&
Utils::filter($arguments, static function (InputValueDefinitionNode $argument) : bool {
return $argument->type instanceof NonNullTypeNode &&
(
! isset($argument->defaultValue) ||
$argument->defaultValue === null
);
}) : [],
static function (NamedTypeNode $argument) : string {
}),
static function (InputValueDefinitionNode $argument) : string {
return $argument->name->value;
}
);
}
return [
NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($requiredArgsMap, $context) {
$directiveName = $directiveNode->name->value;
$requiredArgs = $requiredArgsMap[$directiveName] ?? null;
if (! $requiredArgs) {
return;
}
$argNodes = $directiveNode->arguments ?: [];
$argNodeMap = Utils::keyMap(
$argNodes,
static function (ArgumentNode $arg) : string {
return $arg->name->value;
}
);
foreach ($requiredArgs as $argName => $arg) {
if (isset($argNodeMap[$argName])) {
continue;
NodeKind::DIRECTIVE => [
// Validate on leave to allow for deeper errors to appear first.
'leave' => static function (DirectiveNode $directiveNode) use ($requiredArgsMap, $context) : ?string {
$directiveName = $directiveNode->name->value;
$requiredArgs = $requiredArgsMap[$directiveName] ?? null;
if (! $requiredArgs) {
return null;
}
$context->reportError(
new Error(static::missingDirectiveArgMessage($directiveName, $argName), [$directiveNode])
$argNodes = $directiveNode->arguments ?? [];
$argNodeMap = Utils::keyMap(
$argNodes,
static function (ArgumentNode $arg) : string {
return $arg->name->value;
}
);
}
},
foreach ($requiredArgs as $argName => $arg) {
if (isset($argNodeMap[$argName])) {
continue;
}
if ($arg instanceof FieldArgument) {
$argType = (string) $arg->getType();
} elseif ($arg instanceof InputValueDefinitionNode) {
$argType = Printer::doPrint($arg->type);
} else {
$argType = '';
}
$context->reportError(
new Error(static::missingDirectiveArgMessage($directiveName, $argName, $argType), [$directiveNode])
);
}
return null;
},
],
];
}
}
@@ -15,11 +15,12 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Validator\ValidationContext;
use function array_map;
use function call_user_func_array;
use function count;
use function implode;
use function method_exists;
use function sprintf;
@@ -60,7 +61,7 @@ class QueryComplexity extends QuerySecurityRule
return $this->invokeIfNeeded(
$context,
[
NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) : void {
$this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs(
$context,
$context->getParentType(),
@@ -69,16 +70,16 @@ class QueryComplexity extends QuerySecurityRule
$this->fieldNodeAndDefs
);
},
NodeKind::VARIABLE_DEFINITION => function ($def) {
NodeKind::VARIABLE_DEFINITION => function ($def) : VisitorOperation {
$this->variableDefs[] = $def;
return Visitor::skipNode();
},
NodeKind::OPERATION_DEFINITION => [
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) {
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) : void {
$errors = $context->getErrors();
if (! empty($errors)) {
if (count($errors) > 0) {
return;
}
@@ -113,9 +114,8 @@ class QueryComplexity extends QuerySecurityRule
private function nodeComplexity(Node $node, $complexity = 0)
{
switch ($node->kind) {
case NodeKind::FIELD:
/** @var FieldNode $node */
switch (true) {
case $node instanceof FieldNode:
// default values
$args = [];
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
@@ -143,19 +143,17 @@ class QueryComplexity extends QuerySecurityRule
}
}
$complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]);
$complexity += $complexityFn($childrenComplexity, $args);
break;
case NodeKind::INLINE_FRAGMENT:
/** @var InlineFragmentNode $node */
case $node instanceof InlineFragmentNode:
// node has children?
if (isset($node->selectionSet)) {
$complexity = $this->fieldComplexity($node, $complexity);
}
break;
case NodeKind::FRAGMENT_SPREAD:
/** @var FragmentSpreadNode $node */
case $node instanceof FragmentSpreadNode:
$fragment = $this->getFragment($node);
if ($fragment !== null) {
@@ -194,7 +192,7 @@ class QueryComplexity extends QuerySecurityRule
$this->variableDefs,
$this->getRawVariableValues()
);
if (! empty($errors)) {
if (count($errors ?? []) > 0) {
throw new Error(implode(
"\n\n",
array_map(
@@ -212,11 +210,16 @@ class QueryComplexity extends QuerySecurityRule
return ! $directiveArgsIf;
}
$directive = Directive::skipDirective();
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues);
if ($directiveNode->name->value === Directive::SKIP_NAME) {
$directive = Directive::skipDirective();
/** @var bool $directiveArgsIf */
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if'];
return $directiveArgsIf['if'];
return $directiveArgsIf;
}
}
return false;
}
public function getRawVariableValues()
@@ -229,7 +232,7 @@ class QueryComplexity extends QuerySecurityRule
*/
public function setRawVariableValues(?array $rawVariableValues = null)
{
$this->rawVariableValues = $rawVariableValues ?: [];
$this->rawVariableValues = $rawVariableValues ?? [];
}
private function buildFieldArguments(FieldNode $node)
@@ -247,7 +250,7 @@ class QueryComplexity extends QuerySecurityRule
$rawVariableValues
);
if (! empty($errors)) {
if (count($errors ?? []) > 0) {
throw new Error(implode(
"\n\n",
array_map(
@@ -5,6 +5,9 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
@@ -28,7 +31,7 @@ class QueryDepth extends QuerySecurityRule
$context,
[
NodeKind::OPERATION_DEFINITION => [
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) {
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) : void {
$maxDepth = $this->fieldDepth($operationDefinition);
if ($maxDepth <= $this->getMaxQueryDepth()) {
@@ -57,9 +60,8 @@ class QueryDepth extends QuerySecurityRule
private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0)
{
switch ($node->kind) {
case NodeKind::FIELD:
/** @var FieldNode $node */
switch (true) {
case $node instanceof FieldNode:
// node has children?
if ($node->selectionSet !== null) {
// update maxDepth if needed
@@ -70,16 +72,14 @@ class QueryDepth extends QuerySecurityRule
}
break;
case NodeKind::INLINE_FRAGMENT:
/** @var InlineFragmentNode $node */
case $node instanceof InlineFragmentNode:
// node has children?
if ($node->selectionSet !== null) {
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
}
break;
case NodeKind::FRAGMENT_SPREAD:
/** @var FragmentSpreadNode $node */
case $node instanceof FragmentSpreadNode:
$fragment = $this->getFragment($node);
if ($fragment !== null) {
@@ -9,15 +9,15 @@ use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Definition\HasFieldsType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Utils\TypeInfo;
use GraphQL\Validator\ValidationContext;
use InvalidArgumentException;
use function class_alias;
use function method_exists;
use function sprintf;
abstract class QuerySecurityRule extends ValidationRule
@@ -110,17 +110,15 @@ abstract class QuerySecurityRule extends ValidationRule
?ArrayObject $visitedFragmentNames = null,
?ArrayObject $astAndDefs = null
) {
$_visitedFragmentNames = $visitedFragmentNames ?: new ArrayObject();
$_astAndDefs = $astAndDefs ?: new ArrayObject();
$_visitedFragmentNames = $visitedFragmentNames ?? new ArrayObject();
$_astAndDefs = $astAndDefs ?? new ArrayObject();
foreach ($selectionSet->selections as $selection) {
switch ($selection->kind) {
case NodeKind::FIELD:
/** @var FieldNode $selection */
switch (true) {
case $selection instanceof FieldNode:
$fieldName = $selection->name->value;
$fieldDef = null;
if ($parentType && method_exists($parentType, 'getFields')) {
$tmp = $parentType->getFields();
if ($parentType instanceof HasFieldsType || $parentType instanceof InputObjectType) {
$schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
$typeMetaFieldDef = Introspection::typeMetaFieldDef();
$typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
@@ -131,8 +129,8 @@ abstract class QuerySecurityRule extends ValidationRule
$fieldDef = $typeMetaFieldDef;
} elseif ($fieldName === $typeNameMetaFieldDef->name) {
$fieldDef = $typeNameMetaFieldDef;
} elseif (isset($tmp[$fieldName])) {
$fieldDef = $tmp[$fieldName];
} elseif ($parentType->hasField($fieldName)) {
$fieldDef = $parentType->getField($fieldName);
}
}
$responseName = $this->getFieldName($selection);
@@ -142,8 +140,7 @@ abstract class QuerySecurityRule extends ValidationRule
// create field context
$_astAndDefs[$responseName][] = [$selection, $fieldDef];
break;
case NodeKind::INLINE_FRAGMENT:
/** @var InlineFragmentNode $selection */
case $selection instanceof InlineFragmentNode:
$_astAndDefs = $this->collectFieldASTsAndDefs(
$context,
TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
@@ -152,11 +149,10 @@ abstract class QuerySecurityRule extends ValidationRule
$_astAndDefs
);
break;
case NodeKind::FRAGMENT_SPREAD:
/** @var FragmentSpreadNode $selection */
case $selection instanceof FragmentSpreadNode:
$fragName = $selection->name->value;
if (empty($_visitedFragmentNames[$fragName])) {
if (! ($_visitedFragmentNames[$fragName] ?? false)) {
$_visitedFragmentNames[$fragName] = true;
$fragment = $context->getFragment($fragName);
@@ -16,7 +16,7 @@ class ScalarLeafs extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::FIELD => static function (FieldNode $node) use ($context) {
NodeKind::FIELD => static function (FieldNode $node) use ($context) : void {
$type = $context->getType();
if (! $type) {
return;
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ValidationContext;
use function array_splice;
use function count;
use function sprintf;
class SingleFieldSubscription extends ValidationRule
{
/**
* @return array<string, callable>
*/
public function getVisitor(ValidationContext $context) : array
{
return [
NodeKind::OPERATION_DEFINITION => static function (OperationDefinitionNode $node) use ($context) : VisitorOperation {
if ($node->operation === 'subscription') {
$selections = $node->selectionSet->selections;
if (count($selections) !== 1) {
if ($selections instanceof NodeList) {
$offendingSelections = $selections->splice(1, count($selections));
} else {
$offendingSelections = array_splice($selections, 1);
}
$context->reportError(new Error(
self::multipleFieldsInOperation($node->name->value ?? null),
$offendingSelections
));
}
}
return Visitor::skipNode();
},
];
}
public static function multipleFieldsInOperation(?string $operationName) : string
{
if ($operationName === null) {
return sprintf('Anonymous Subscription must select only one top level field.');
}
return sprintf('Subscription "%s" must select only one top level field.', $operationName);
}
}
@@ -9,6 +9,9 @@ use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ASTValidationContext;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function sprintf;
@@ -17,20 +20,30 @@ class UniqueArgumentNames extends ValidationRule
/** @var NameNode[] */
public $knownArgNames;
public function getSDLVisitor(SDLValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getVisitor(ValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getASTVisitor(ASTValidationContext $context)
{
$this->knownArgNames = [];
return [
NodeKind::FIELD => function () {
NodeKind::FIELD => function () : void {
$this->knownArgNames = [];
},
NodeKind::DIRECTIVE => function () {
NodeKind::DIRECTIVE => function () : void {
$this->knownArgNames = [];
},
NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) {
NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) : VisitorOperation {
$argName = $node->name->value;
if (! empty($this->knownArgNames[$argName])) {
if ($this->knownArgNames[$argName] ?? false) {
$context->reportError(new Error(
self::duplicateArgMessage($argName),
[$this->knownArgNames[$argName], $node->name]
@@ -5,25 +5,70 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\Directive;
use GraphQL\Validator\ASTValidationContext;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function sprintf;
/**
* Unique directive names per location
*
* A GraphQL document is only valid if all non-repeatable directives at
* a given location are uniquely named.
*/
class UniqueDirectivesPerLocation extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getSDLVisitor(SDLValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getASTVisitor(ASTValidationContext $context)
{
$uniqueDirectiveMap = [];
$schema = $context->getSchema();
$definedDirectives = $schema !== null
? $schema->getDirectives()
: Directive::getInternalDirectives();
foreach ($definedDirectives as $directive) {
$uniqueDirectiveMap[$directive->name] = ! $directive->isRepeatable;
}
$astDefinitions = $context->getDocument()->definitions;
foreach ($astDefinitions as $definition) {
if (! ($definition instanceof DirectiveDefinitionNode)) {
continue;
}
$uniqueDirectiveMap[$definition->name->value] = $definition->repeatable;
}
return [
'enter' => static function (Node $node) use ($context) {
'enter' => static function (Node $node) use ($uniqueDirectiveMap, $context) : void {
if (! isset($node->directives)) {
return;
}
$knownDirectives = [];
/** @var DirectiveNode $directive */
foreach ($node->directives as $directive) {
/** @var DirectiveNode $directive */
$directiveName = $directive->name->value;
if (! isset($uniqueDirectiveMap[$directiveName])) {
continue;
}
if (isset($knownDirectives[$directiveName])) {
$context->reportError(new Error(
self::duplicateDirectiveMessage($directiveName),
@@ -9,6 +9,7 @@ use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ValidationContext;
use function sprintf;
@@ -22,12 +23,12 @@ class UniqueFragmentNames extends ValidationRule
$this->knownFragmentNames = [];
return [
NodeKind::OPERATION_DEFINITION => static function () {
NodeKind::OPERATION_DEFINITION => static function () : VisitorOperation {
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) : VisitorOperation {
$fragmentName = $node->name->value;
if (empty($this->knownFragmentNames[$fragmentName])) {
if (! isset($this->knownFragmentNames[$fragmentName])) {
$this->knownFragmentNames[$fragmentName] = $node->name;
} else {
$context->reportError(new Error(
@@ -5,40 +5,54 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\ObjectFieldNode;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ASTValidationContext;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function array_pop;
use function sprintf;
class UniqueInputFieldNames extends ValidationRule
{
/** @var string[] */
/** @var array<string, NameNode> */
public $knownNames;
/** @var string[][] */
/** @var array<array<string, NameNode>> */
public $knownNameStack;
public function getVisitor(ValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getSDLVisitor(SDLValidationContext $context)
{
return $this->getASTVisitor($context);
}
public function getASTVisitor(ASTValidationContext $context)
{
$this->knownNames = [];
$this->knownNameStack = [];
return [
NodeKind::OBJECT => [
'enter' => function () {
'enter' => function () : void {
$this->knownNameStack[] = $this->knownNames;
$this->knownNames = [];
},
'leave' => function () {
'leave' => function () : void {
$this->knownNames = array_pop($this->knownNameStack);
},
],
NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) {
NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) : VisitorOperation {
$fieldName = $node->name->value;
if (! empty($this->knownNames[$fieldName])) {
if (isset($this->knownNames[$fieldName])) {
$context->reportError(new Error(
self::duplicateInputFieldMessage($fieldName),
[$this->knownNames[$fieldName], $node->name]
@@ -9,6 +9,7 @@ use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Validator\ValidationContext;
use function sprintf;
@@ -22,11 +23,11 @@ class UniqueOperationNames extends ValidationRule
$this->knownOperationNames = [];
return [
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) {
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) : VisitorOperation {
$operationName = $node->name;
if ($operationName) {
if (empty($this->knownOperationNames[$operationName->value])) {
if ($operationName !== null) {
if (! isset($this->knownOperationNames[$operationName->value])) {
$this->knownOperationNames[$operationName->value] = $operationName;
} else {
$context->reportError(new Error(
@@ -38,7 +39,7 @@ class UniqueOperationNames extends ValidationRule
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => static function () {
NodeKind::FRAGMENT_DEFINITION => static function () : VisitorOperation {
return Visitor::skipNode();
},
];
@@ -21,12 +21,12 @@ class UniqueVariableNames extends ValidationRule
$this->knownVariableNames = [];
return [
NodeKind::OPERATION_DEFINITION => function () {
NodeKind::OPERATION_DEFINITION => function () : void {
$this->knownVariableNames = [];
},
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) : void {
$variableName = $node->variable->name->value;
if (empty($this->knownVariableNames[$variableName])) {
if (! isset($this->knownVariableNames[$variableName])) {
$this->knownVariableNames[$variableName] = $node->variable->name;
} else {
$context->reportError(new Error(
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Validator\SDLValidationContext;
use GraphQL\Validator\ValidationContext;
use function class_alias;
@@ -14,7 +15,7 @@ abstract class ValidationRule
public function getName()
{
return $this->name ?: static::class;
return $this->name === '' || $this->name === null ? static::class : $this->name;
}
public function __invoke(ValidationContext $context)
@@ -29,7 +30,22 @@ abstract class ValidationRule
*
* @return mixed[]
*/
abstract public function getVisitor(ValidationContext $context);
public function getVisitor(ValidationContext $context)
{
return [];
}
/**
* Returns structure suitable for GraphQL\Language\Visitor
*
* @see \GraphQL\Language\Visitor
*
* @return mixed[]
*/
public function getSDLVisitor(SDLValidationContext $context)
{
return [];
}
}
class_alias(ValidationRule::class, 'GraphQL\Validator\Rules\AbstractValidationRule');
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\EnumValueNode;
@@ -18,11 +17,12 @@ use GraphQL\Language\AST\ObjectFieldNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Printer;
use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\EnumValueDefinition;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
@@ -52,11 +52,11 @@ class ValuesOfCorrectType extends ValidationRule
return [
NodeKind::FIELD => [
'enter' => static function (FieldNode $node) use (&$fieldName) {
'enter' => static function (FieldNode $node) use (&$fieldName) : void {
$fieldName = $node->name->value;
},
],
NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) {
NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) : void {
$type = $context->getInputType();
if (! ($type instanceof NonNull)) {
return;
@@ -69,7 +69,7 @@ class ValuesOfCorrectType extends ValidationRule
)
);
},
NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) {
NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) : ?VisitorOperation {
// Note: TypeInfo will traverse into a list's item type, so look to the
// parent input type to check if it is a list.
$type = Type::getNullableType($context->getParentInputType());
@@ -78,8 +78,10 @@ class ValuesOfCorrectType extends ValidationRule
return Visitor::skipNode();
}
return null;
},
NodeKind::OBJECT => function (ObjectValueNode $node) use ($context, &$fieldName) {
NodeKind::OBJECT => function (ObjectValueNode $node) use ($context, &$fieldName) : ?VisitorOperation {
// Note: TypeInfo will traverse into a list's item type, so look to the
// parent input type to check if it is a list.
$type = Type::getNamedType($context->getInputType());
@@ -94,7 +96,7 @@ class ValuesOfCorrectType extends ValidationRule
$nodeFields = iterator_to_array($node->fields);
$fieldNodeMap = array_combine(
array_map(
static function ($field) {
static function ($field) : string {
return $field->name->value;
},
$nodeFields
@@ -103,7 +105,7 @@ class ValuesOfCorrectType extends ValidationRule
);
foreach ($inputFields as $fieldName => $fieldDef) {
$fieldType = $fieldDef->getType();
if (isset($fieldNodeMap[$fieldName]) || ! ($fieldType instanceof NonNull)) {
if (isset($fieldNodeMap[$fieldName]) || ! $fieldDef->isRequired()) {
continue;
}
@@ -114,10 +116,13 @@ class ValuesOfCorrectType extends ValidationRule
)
);
}
return null;
},
NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context) {
NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context) : void {
$parentType = Type::getNamedType($context->getParentInputType());
$fieldType = $context->getInputType();
/** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $fieldType */
$fieldType = $context->getInputType();
if ($fieldType || ! ($parentType instanceof InputObjectType)) {
return;
}
@@ -137,7 +142,7 @@ class ValuesOfCorrectType extends ValidationRule
)
);
},
NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) {
NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) : void {
$type = Type::getNamedType($context->getInputType());
if (! $type instanceof EnumType) {
$this->isValidScalar($context, $node, $fieldName);
@@ -156,16 +161,16 @@ class ValuesOfCorrectType extends ValidationRule
);
}
},
NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) {
NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) : void {
$this->isValidScalar($context, $node, $fieldName);
},
NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) {
NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) : void {
$this->isValidScalar($context, $node, $fieldName);
},
NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) {
NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) : void {
$this->isValidScalar($context, $node, $fieldName);
},
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) {
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) : void {
$this->isValidScalar($context, $node, $fieldName);
},
];
@@ -177,9 +182,13 @@ class ValuesOfCorrectType extends ValidationRule
($message ? "; ${message}" : '.');
}
/**
* @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node
*/
private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName)
{
// Report any error at the full type expected by the location.
/** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $locationType */
$locationType = $context->getInputType();
if (! $locationType) {
@@ -209,20 +218,6 @@ class ValuesOfCorrectType extends ValidationRule
// may throw to indicate failure.
try {
$type->parseLiteral($node);
} catch (Exception $error) {
// Ensure a reference to the original error is maintained.
$context->reportError(
new Error(
self::getBadValueMessage(
(string) $locationType,
Printer::doPrint($node),
$error->getMessage(),
$context,
$fieldName
),
$node
)
);
} catch (Throwable $error) {
// Ensure a reference to the original error is maintained.
$context->reportError(
@@ -234,19 +229,26 @@ class ValuesOfCorrectType extends ValidationRule
$context,
$fieldName
),
$node
$node,
null,
[],
null,
$error
)
);
}
}
/**
* @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node
*/
private function enumTypeSuggestion($type, ValueNode $node)
{
if ($type instanceof EnumType) {
$suggestions = Utils::suggestionList(
Printer::doPrint($node),
array_map(
static function (EnumValueDefinition $value) {
static function (EnumValueDefinition $value) : string {
return $value->name;
},
$type->getValues()
@@ -18,7 +18,7 @@ class VariablesAreInputTypes extends ValidationRule
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $node) use ($context) {
NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $node) use ($context) : void {
$type = TypeInfo::typeFromAST($context->getSchema(), $node->type);
// If the variable type is not an input type, return an error.
@@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Validator\ValidationContext;
use function sprintf;
/**
* Variable's default value is allowed
*
* A GraphQL document is only valid if all variable default values are allowed
* due to a variable not being required.
*/
class VariablesDefaultValueAllowed extends ValidationRule
{
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $node) use ($context) {
$name = $node->variable->name->value;
$defaultValue = $node->defaultValue;
$type = $context->getInputType();
if ($type instanceof NonNull && $defaultValue) {
$context->reportError(
new Error(
self::defaultForRequiredVarMessage(
$name,
$type,
$type->getWrappedType()
),
[$defaultValue]
)
);
}
return Visitor::skipNode();
},
NodeKind::SELECTION_SET => static function (SelectionSetNode $node) {
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => static function (FragmentDefinitionNode $node) {
return Visitor::skipNode();
},
];
}
public static function defaultForRequiredVarMessage($varName, $type, $guessType)
{
return sprintf(
'Variable "$%s" of type "%s" is required and will not use the default value. Perhaps you meant to use type "%s".',
$varName,
$type,
$guessType
);
}
}
@@ -6,35 +6,44 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\TypeComparators;
use GraphQL\Utils\TypeInfo;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class VariablesInAllowedPosition extends ValidationRule
{
/** @var */
/**
* A map from variable names to their definition nodes.
*
* @var VariableDefinitionNode[]
*/
public $varDefMap;
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::OPERATION_DEFINITION => [
'enter' => function () {
'enter' => function () : void {
$this->varDefMap = [];
},
'leave' => function (OperationDefinitionNode $operation) use ($context) {
'leave' => function (OperationDefinitionNode $operation) use ($context) : void {
$usages = $context->getRecursiveVariableUsages($operation);
foreach ($usages as $usage) {
$node = $usage['node'];
$type = $usage['type'];
$varName = $node->name->value;
$varDef = $this->varDefMap[$varName] ?? null;
$node = $usage['node'];
$type = $usage['type'];
$defaultValue = $usage['defaultValue'];
$varName = $node->name->value;
$varDef = $this->varDefMap[$varName] ?? null;
if ($varDef === null || $type === null) {
continue;
@@ -48,11 +57,7 @@ class VariablesInAllowedPosition extends ValidationRule
$schema = $context->getSchema();
$varType = TypeInfo::typeFromAST($schema, $varDef->type);
if (! $varType || TypeComparators::isTypeSubTypeOf(
$schema,
$this->effectiveType($varType, $varDef),
$type
)) {
if (! $varType || $this->allowedVariableUsage($schema, $varType, $varDef->defaultValue, $type, $defaultValue)) {
continue;
}
@@ -63,17 +68,12 @@ class VariablesInAllowedPosition extends ValidationRule
}
},
],
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) {
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) : void {
$this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
},
];
}
private function effectiveType($varType, $varDef)
{
return ! $varDef->defaultValue || $varType instanceof NonNull ? $varType : new NonNull($varType);
}
/**
* A var type is allowed if it is the same or more strict than the expected
* type. It can be more strict if the variable type is non-null when the
@@ -90,23 +90,27 @@ class VariablesInAllowedPosition extends ValidationRule
);
}
/** If a variable definition has a default value, it's effectively non-null. */
private function varTypeAllowedForType($varType, $expectedType)
/**
* Returns true if the variable is allowed in the location it was found,
* which includes considering if default values exist for either the variable
* or the location at which it is located.
*
* @param ValueNode|null $varDefaultValue
* @param mixed $locationDefaultValue
*/
private function allowedVariableUsage(Schema $schema, Type $varType, $varDefaultValue, Type $locationType, $locationDefaultValue) : bool
{
if ($expectedType instanceof NonNull) {
if ($varType instanceof NonNull) {
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
if ($locationType instanceof NonNull && ! $varType instanceof NonNull) {
$hasNonNullVariableDefaultValue = $varDefaultValue && ! $varDefaultValue instanceof NullValueNode;
$hasLocationDefaultValue = ! Utils::isInvalid($locationDefaultValue);
if (! $hasNonNullVariableDefaultValue && ! $hasLocationDefaultValue) {
return false;
}
$nullableLocationType = $locationType->getWrappedType();
return false;
}
if ($varType instanceof NonNull) {
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType);
}
if ($varType instanceof ListOfType && $expectedType instanceof ListOfType) {
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
return TypeComparators::isTypeSubTypeOf($schema, $varType, $nullableLocationType);
}
return $varType === $expectedType;
return TypeComparators::isTypeSubTypeOf($schema, $varType, $locationType);
}
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator;
class SDLValidationContext extends ASTValidationContext
{
}
@@ -4,24 +4,33 @@ declare(strict_types=1);
namespace GraphQL\Validator;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\HasSelectionSet;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\TypeInfo;
use SplObjectStorage;
use function array_merge;
use function array_pop;
use function call_user_func_array;
use function count;
/**
@@ -29,20 +38,11 @@ use function count;
* allowing access to commonly useful contextual information from within a
* validation rule.
*/
class ValidationContext
class ValidationContext extends ASTValidationContext
{
/** @var Schema */
private $schema;
/** @var DocumentNode */
private $ast;
/** @var TypeInfo */
private $typeInfo;
/** @var Error[] */
private $errors;
/** @var FragmentDefinitionNode[] */
private $fragments;
@@ -60,37 +60,14 @@ class ValidationContext
public function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
{
$this->schema = $schema;
$this->ast = $ast;
parent::__construct($ast, $schema);
$this->typeInfo = $typeInfo;
$this->errors = [];
$this->fragmentSpreads = new SplObjectStorage();
$this->recursivelyReferencedFragments = new SplObjectStorage();
$this->variableUsages = new SplObjectStorage();
$this->recursiveVariableUsages = new SplObjectStorage();
}
public function reportError(Error $error)
{
$this->errors[] = $error;
}
/**
* @return Error[]
*/
public function getErrors()
{
return $this->errors;
}
/**
* @return Schema
*/
public function getSchema()
{
return $this->schema;
}
/**
* @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
*/
@@ -102,11 +79,11 @@ class ValidationContext
$usages = $this->getVariableUsages($operation);
$fragments = $this->getRecursivelyReferencedFragments($operation);
$tmp = [$usages];
foreach ($fragments as $i => $fragment) {
$tmp[] = $this->getVariableUsages($fragments[$i]);
$allUsages = [$usages];
foreach ($fragments as $fragment) {
$allUsages[] = $this->getVariableUsages($fragment);
}
$usages = call_user_func_array('array_merge', $tmp);
$usages = array_merge(...$allUsages);
$this->recursiveVariableUsages[$operation] = $usages;
}
@@ -128,14 +105,18 @@ class ValidationContext
Visitor::visitWithTypeInfo(
$typeInfo,
[
NodeKind::VARIABLE_DEFINITION => static function () {
NodeKind::VARIABLE_DEFINITION => static function () : bool {
return false;
},
NodeKind::VARIABLE => static function (VariableNode $variable) use (
&$newUsages,
$typeInfo
) {
$newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
) : void {
$newUsages[] = [
'node' => $variable,
'type' => $typeInfo->getInputType(),
'defaultValue' => $typeInfo->getDefaultValue(),
];
},
]
)
@@ -158,13 +139,13 @@ class ValidationContext
$fragments = [];
$collectedNames = [];
$nodesToVisit = [$operation];
while (! empty($nodesToVisit)) {
while (count($nodesToVisit) > 0) {
$node = array_pop($nodesToVisit);
$spreads = $this->getFragmentSpreads($node);
foreach ($spreads as $spread) {
$fragName = $spread->name->value;
if (! empty($collectedNames[$fragName])) {
if ($collectedNames[$fragName] ?? false) {
continue;
}
@@ -185,24 +166,30 @@ class ValidationContext
}
/**
* @param OperationDefinitionNode|FragmentDefinitionNode $node
*
* @return FragmentSpreadNode[]
*/
public function getFragmentSpreads(HasSelectionSet $node)
public function getFragmentSpreads(HasSelectionSet $node) : array
{
$spreads = $this->fragmentSpreads[$node] ?? null;
if ($spreads === null) {
$spreads = [];
/** @var SelectionSetNode[] $setsToVisit */
$setsToVisit = [$node->selectionSet];
while (! empty($setsToVisit)) {
while (count($setsToVisit) > 0) {
$set = array_pop($setsToVisit);
for ($i = 0, $selectionCount = count($set->selections); $i < $selectionCount; $i++) {
$selection = $set->selections[$i];
if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
if ($selection instanceof FragmentSpreadNode) {
$spreads[] = $selection;
} elseif ($selection->selectionSet) {
$setsToVisit[] = $selection->selectionSet;
} elseif ($selection instanceof FieldNode || $selection instanceof InlineFragmentNode) {
if ($selection->selectionSet) {
$setsToVisit[] = $selection->selectionSet;
}
} else {
throw InvariantViolation::shouldNotHappen();
}
}
}
@@ -223,7 +210,7 @@ class ValidationContext
if (! $fragments) {
$fragments = [];
foreach ($this->getDocument()->definitions as $statement) {
if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) {
if (! ($statement instanceof FragmentDefinitionNode)) {
continue;
}
@@ -235,44 +222,31 @@ class ValidationContext
return $fragments[$name] ?? null;
}
/**
* @return DocumentNode
*/
public function getDocument()
{
return $this->ast;
}
/**
* Returns OutputType
*
* @return Type
*/
public function getType()
public function getType() : ?OutputType
{
return $this->typeInfo->getType();
}
/**
* @return Type
* @return (CompositeType & Type) | null
*/
public function getParentType()
public function getParentType() : ?CompositeType
{
return $this->typeInfo->getParentType();
}
/**
* @return InputType
* @return (Type & InputType) | null
*/
public function getInputType()
public function getInputType() : ?InputType
{
return $this->typeInfo->getInputType();
}
/**
* @return InputType
* @return (Type&InputType)|null
*/
public function getParentInputType()
public function getParentInputType() : ?InputType
{
return $this->typeInfo->getParentInputType();
}