mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-29 00:25:17 +03:00
Fix CS in Validator folder
This commit is contained in:
parent
49ec89b28f
commit
4c327a6c16
@ -1,6 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## dev-master
|
## dev-master
|
||||||
- Spec compliance: error extensions are displayed under `extensions` key
|
- Spec compliance: error extensions are displayed under `extensions` key
|
||||||
|
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
|
||||||
|
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
|
||||||
|
|
||||||
#### v0.12.5
|
#### v0.12.5
|
||||||
- Execution performance optimization for lists
|
- Execution performance optimization for lists
|
||||||
|
@ -119,7 +119,7 @@ static function getStandardTypes()
|
|||||||
* Returns standard validation rules implementing GraphQL spec
|
* Returns standard validation rules implementing GraphQL spec
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @return AbstractValidationRule[]
|
* @return ValidationRule[]
|
||||||
*/
|
*/
|
||||||
static function getStandardValidationRules()
|
static function getStandardValidationRules()
|
||||||
```
|
```
|
||||||
@ -1241,7 +1241,7 @@ an empty array if no errors were encountered and the document is valid.
|
|||||||
A list of specific validation rules may be provided. If not provided, the
|
A list of specific validation rules may be provided. If not provided, the
|
||||||
default list of rules defined by the GraphQL specification will be used.
|
default list of rules defined by the GraphQL specification will be used.
|
||||||
|
|
||||||
Each validation rule is an instance of GraphQL\Validator\Rules\AbstractValidationRule
|
Each validation rule is an instance of GraphQL\Validator\Rules\ValidationRule
|
||||||
which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
|
which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
|
||||||
|
|
||||||
Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
|
Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
|
||||||
@ -1258,7 +1258,7 @@ will be created from the provided schema.
|
|||||||
* @api
|
* @api
|
||||||
* @param Schema $schema
|
* @param Schema $schema
|
||||||
* @param DocumentNode $ast
|
* @param DocumentNode $ast
|
||||||
* @param AbstractValidationRule[]|null $rules
|
* @param ValidationRule[]|null $rules
|
||||||
* @param TypeInfo|null $typeInfo
|
* @param TypeInfo|null $typeInfo
|
||||||
* @return Error[]
|
* @return Error[]
|
||||||
*/
|
*/
|
||||||
@ -1275,7 +1275,7 @@ static function validate(
|
|||||||
* Returns all global validation rules.
|
* Returns all global validation rules.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @return AbstractValidationRule[]
|
* @return ValidationRule[]
|
||||||
*/
|
*/
|
||||||
static function allRules()
|
static function allRules()
|
||||||
```
|
```
|
||||||
@ -1289,7 +1289,7 @@ static function allRules()
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return AbstractValidationRule
|
* @return ValidationRule
|
||||||
*/
|
*/
|
||||||
static function getRule($name)
|
static function getRule($name)
|
||||||
```
|
```
|
||||||
@ -1299,9 +1299,9 @@ static function getRule($name)
|
|||||||
* Add rule to list of global validation rules
|
* Add rule to list of global validation rules
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param AbstractValidationRule $rule
|
* @param ValidationRule $rule
|
||||||
*/
|
*/
|
||||||
static function addRule(GraphQL\Validator\Rules\AbstractValidationRule $rule)
|
static function addRule(GraphQL\Validator\Rules\ValidationRule $rule)
|
||||||
```
|
```
|
||||||
# GraphQL\Error\Error
|
# GraphQL\Error\Error
|
||||||
Describes an Error found during the parse, validate, or
|
Describes an Error found during the parse, validate, or
|
||||||
|
@ -8,9 +8,9 @@ use GraphQL\Language\AST\Node;
|
|||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
|
use Traversable;
|
||||||
use function array_filter;
|
use function array_filter;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function array_merge;
|
|
||||||
use function is_array;
|
use function is_array;
|
||||||
use function iterator_to_array;
|
use function iterator_to_array;
|
||||||
|
|
||||||
@ -81,12 +81,12 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||||||
protected $extensions;
|
protected $extensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @param Node[]|null $nodes
|
* @param Node|Node[]|Traversable|null $nodes
|
||||||
* @param mixed[]|null $positions
|
* @param mixed[]|null $positions
|
||||||
* @param mixed[]|null $path
|
* @param mixed[]|null $path
|
||||||
* @param \Throwable $previous
|
* @param \Throwable $previous
|
||||||
* @param mixed[] $extensions
|
* @param mixed[] $extensions
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
$message,
|
$message,
|
||||||
|
@ -13,7 +13,7 @@ use GraphQL\Executor\Promise\PromiseAdapter;
|
|||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
use GraphQL\Validator\Rules\AbstractValidationRule;
|
use GraphQL\Validator\Rules\ValidationRule;
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,7 +272,7 @@ class GraphQL
|
|||||||
* Returns standard validation rules implementing GraphQL spec
|
* Returns standard validation rules implementing GraphQL spec
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @return AbstractValidationRule[]
|
* @return ValidationRule[]
|
||||||
*/
|
*/
|
||||||
public static function getStandardValidationRules()
|
public static function getStandardValidationRules()
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FieldNode extends Node implements SelectionNode
|
class FieldNode extends Node implements SelectionNode
|
||||||
@ -29,4 +32,9 @@ class FieldNode extends Node implements SelectionNode
|
|||||||
* @var SelectionSetNode|null
|
* @var SelectionSetNode|null
|
||||||
*/
|
*/
|
||||||
public $selectionSet;
|
public $selectionSet;
|
||||||
|
|
||||||
|
public function getKind() : string
|
||||||
|
{
|
||||||
|
return NodeKind::FIELD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FragmentSpreadNode extends Node implements SelectionNode
|
class FragmentSpreadNode extends Node implements SelectionNode
|
||||||
@ -14,4 +15,9 @@ class FragmentSpreadNode extends Node implements SelectionNode
|
|||||||
* @var DirectiveNode[]
|
* @var DirectiveNode[]
|
||||||
*/
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
|
public function getKind() : string
|
||||||
|
{
|
||||||
|
return NodeKind::FRAGMENT_SPREAD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class InlineFragmentNode extends Node implements SelectionNode
|
class InlineFragmentNode extends Node implements SelectionNode
|
||||||
@ -19,4 +20,9 @@ class InlineFragmentNode extends Node implements SelectionNode
|
|||||||
* @var SelectionSetNode
|
* @var SelectionSetNode
|
||||||
*/
|
*/
|
||||||
public $selectionSet;
|
public $selectionSet;
|
||||||
|
|
||||||
|
public function getKind() : string
|
||||||
|
{
|
||||||
|
return NodeKind::INLINE_FRAGMENT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
interface SelectionNode
|
interface SelectionNode
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* export type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode
|
* export type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode
|
||||||
*/
|
*/
|
||||||
|
public function getKind() : string;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use GraphQL\Error\InvariantViolation;
|
|||||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\Rules\AbstractValidationRule;
|
use GraphQL\Validator\Rules\ValidationRule;
|
||||||
use function is_array;
|
use function is_array;
|
||||||
use function is_callable;
|
use function is_callable;
|
||||||
use function method_exists;
|
use function method_exists;
|
||||||
@ -73,7 +73,7 @@ class ServerConfig
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $queryBatching = false;
|
private $queryBatching = false;
|
||||||
|
|
||||||
/** @var AbstractValidationRule[]|callable */
|
/** @var ValidationRule[]|callable */
|
||||||
private $validationRules;
|
private $validationRules;
|
||||||
|
|
||||||
/** @var callable */
|
/** @var callable */
|
||||||
@ -150,7 +150,7 @@ class ServerConfig
|
|||||||
* Set validation rules for this server.
|
* Set validation rules for this server.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param AbstractValidationRule[]|callable $validationRules
|
* @param ValidationRule[]|callable $validationRules
|
||||||
* @return self
|
* @return self
|
||||||
*/
|
*/
|
||||||
public function setValidationRules($validationRules)
|
public function setValidationRules($validationRules)
|
||||||
@ -281,7 +281,7 @@ class ServerConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return AbstractValidationRule[]|callable
|
* @return ValidationRule[]|callable
|
||||||
*/
|
*/
|
||||||
public function getValidationRules()
|
public function getValidationRules()
|
||||||
{
|
{
|
||||||
|
@ -204,7 +204,7 @@ class Schema
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return Type
|
* @return Type|null
|
||||||
*/
|
*/
|
||||||
public function getType($name)
|
public function getType($name)
|
||||||
{
|
{
|
||||||
|
@ -487,7 +487,7 @@ class AST
|
|||||||
* @api
|
* @api
|
||||||
* @param Schema $schema
|
* @param Schema $schema
|
||||||
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
||||||
* @return Type
|
* @return Type|null
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public static function typeFromAST(Schema $schema, $inputTypeNode)
|
public static function typeFromAST(Schema $schema, $inputTypeNode)
|
||||||
|
@ -58,7 +58,7 @@ class TypeInfo
|
|||||||
/**
|
/**
|
||||||
* @param Schema $schema
|
* @param Schema $schema
|
||||||
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
||||||
* @return Type
|
* @return Type|null
|
||||||
* @throws InvariantViolation
|
* @throws InvariantViolation
|
||||||
*/
|
*/
|
||||||
public static function typeFromAST(Schema $schema, $inputTypeNode)
|
public static function typeFromAST(Schema $schema, $inputTypeNode)
|
||||||
@ -249,7 +249,7 @@ class TypeInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return CompositeType
|
* @return Type
|
||||||
*/
|
*/
|
||||||
function getParentType()
|
function getParentType()
|
||||||
{
|
{
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Validator;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Validator\Rules\AbstractValidationRule;
|
|
||||||
use GraphQL\Validator\Rules\ValuesOfCorrectType;
|
|
||||||
use GraphQL\Validator\Rules\DisableIntrospection;
|
use GraphQL\Validator\Rules\DisableIntrospection;
|
||||||
use GraphQL\Validator\Rules\ExecutableDefinitions;
|
use GraphQL\Validator\Rules\ExecutableDefinitions;
|
||||||
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
||||||
@ -27,6 +28,7 @@ use GraphQL\Validator\Rules\PossibleFragmentSpreads;
|
|||||||
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
use GraphQL\Validator\Rules\QueryDepth;
|
use GraphQL\Validator\Rules\QueryDepth;
|
||||||
|
use GraphQL\Validator\Rules\QuerySecurityRule;
|
||||||
use GraphQL\Validator\Rules\ScalarLeafs;
|
use GraphQL\Validator\Rules\ScalarLeafs;
|
||||||
use GraphQL\Validator\Rules\UniqueArgumentNames;
|
use GraphQL\Validator\Rules\UniqueArgumentNames;
|
||||||
use GraphQL\Validator\Rules\UniqueDirectivesPerLocation;
|
use GraphQL\Validator\Rules\UniqueDirectivesPerLocation;
|
||||||
@ -34,9 +36,16 @@ use GraphQL\Validator\Rules\UniqueFragmentNames;
|
|||||||
use GraphQL\Validator\Rules\UniqueInputFieldNames;
|
use GraphQL\Validator\Rules\UniqueInputFieldNames;
|
||||||
use GraphQL\Validator\Rules\UniqueOperationNames;
|
use GraphQL\Validator\Rules\UniqueOperationNames;
|
||||||
use GraphQL\Validator\Rules\UniqueVariableNames;
|
use GraphQL\Validator\Rules\UniqueVariableNames;
|
||||||
|
use GraphQL\Validator\Rules\ValidationRule;
|
||||||
|
use GraphQL\Validator\Rules\ValuesOfCorrectType;
|
||||||
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
||||||
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
|
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
|
||||||
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
||||||
|
use function array_filter;
|
||||||
|
use function array_merge;
|
||||||
|
use function count;
|
||||||
|
use function is_array;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the "Validation" section of the spec.
|
* Implements the "Validation" section of the spec.
|
||||||
@ -47,7 +56,7 @@ use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
|||||||
* A list of specific validation rules may be provided. If not provided, the
|
* A list of specific validation rules may be provided. If not provided, the
|
||||||
* default list of rules defined by the GraphQL specification will be used.
|
* default list of rules defined by the GraphQL specification will be used.
|
||||||
*
|
*
|
||||||
* Each validation rule is an instance of GraphQL\Validator\Rules\AbstractValidationRule
|
* Each validation rule is an instance of GraphQL\Validator\Rules\ValidationRule
|
||||||
* which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
|
* which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
|
||||||
*
|
*
|
||||||
* Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
|
* Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
|
||||||
@ -58,56 +67,55 @@ use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
|||||||
*/
|
*/
|
||||||
class DocumentValidator
|
class DocumentValidator
|
||||||
{
|
{
|
||||||
|
/** @var ValidationRule[] */
|
||||||
private static $rules = [];
|
private static $rules = [];
|
||||||
|
|
||||||
|
/** @var ValidationRule[]|null */
|
||||||
private static $defaultRules;
|
private static $defaultRules;
|
||||||
|
|
||||||
|
/** @var QuerySecurityRule[]|null */
|
||||||
private static $securityRules;
|
private static $securityRules;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
private static $initRules = false;
|
private static $initRules = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary method for query validation. See class description for details.
|
* Primary method for query validation. See class description for details.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param Schema $schema
|
* @param ValidationRule[]|null $rules
|
||||||
* @param DocumentNode $ast
|
|
||||||
* @param AbstractValidationRule[]|null $rules
|
|
||||||
* @param TypeInfo|null $typeInfo
|
|
||||||
* @return Error[]
|
* @return Error[]
|
||||||
*/
|
*/
|
||||||
public static function validate(
|
public static function validate(
|
||||||
Schema $schema,
|
Schema $schema,
|
||||||
DocumentNode $ast,
|
DocumentNode $ast,
|
||||||
array $rules = null,
|
?array $rules = null,
|
||||||
TypeInfo $typeInfo = null
|
?TypeInfo $typeInfo = null
|
||||||
)
|
) {
|
||||||
{
|
if ($rules === null) {
|
||||||
if (null === $rules) {
|
|
||||||
$rules = static::allRules();
|
$rules = static::allRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true === is_array($rules) && 0 === count($rules)) {
|
if (is_array($rules) === true && count($rules) === 0) {
|
||||||
// Skip validation if there are no rules
|
// Skip validation if there are no rules
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$typeInfo = $typeInfo ?: new TypeInfo($schema);
|
$typeInfo = $typeInfo ?: new TypeInfo($schema);
|
||||||
$errors = static::visitUsingRules($schema, $typeInfo, $ast, $rules);
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return static::visitUsingRules($schema, $typeInfo, $ast, $rules);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all global validation rules.
|
* Returns all global validation rules.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @return AbstractValidationRule[]
|
* @return ValidationRule[]
|
||||||
*/
|
*/
|
||||||
public static function allRules()
|
public static function allRules()
|
||||||
{
|
{
|
||||||
if (!self::$initRules) {
|
if (! self::$initRules) {
|
||||||
static::$rules = array_merge(static::defaultRules(), self::securityRules(), self::$rules);
|
static::$rules = array_merge(static::defaultRules(), self::securityRules(), self::$rules);
|
||||||
static::$initRules = true;
|
static::$initRules = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,34 +124,34 @@ class DocumentValidator
|
|||||||
|
|
||||||
public static function defaultRules()
|
public static function defaultRules()
|
||||||
{
|
{
|
||||||
if (null === self::$defaultRules) {
|
if (self::$defaultRules === null) {
|
||||||
self::$defaultRules = [
|
self::$defaultRules = [
|
||||||
ExecutableDefinitions::class => new ExecutableDefinitions(),
|
ExecutableDefinitions::class => new ExecutableDefinitions(),
|
||||||
UniqueOperationNames::class => new UniqueOperationNames(),
|
UniqueOperationNames::class => new UniqueOperationNames(),
|
||||||
LoneAnonymousOperation::class => new LoneAnonymousOperation(),
|
LoneAnonymousOperation::class => new LoneAnonymousOperation(),
|
||||||
KnownTypeNames::class => new KnownTypeNames(),
|
KnownTypeNames::class => new KnownTypeNames(),
|
||||||
FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
|
FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
|
||||||
VariablesAreInputTypes::class => new VariablesAreInputTypes(),
|
VariablesAreInputTypes::class => new VariablesAreInputTypes(),
|
||||||
ScalarLeafs::class => new ScalarLeafs(),
|
ScalarLeafs::class => new ScalarLeafs(),
|
||||||
FieldsOnCorrectType::class => new FieldsOnCorrectType(),
|
FieldsOnCorrectType::class => new FieldsOnCorrectType(),
|
||||||
UniqueFragmentNames::class => new UniqueFragmentNames(),
|
UniqueFragmentNames::class => new UniqueFragmentNames(),
|
||||||
KnownFragmentNames::class => new KnownFragmentNames(),
|
KnownFragmentNames::class => new KnownFragmentNames(),
|
||||||
NoUnusedFragments::class => new NoUnusedFragments(),
|
NoUnusedFragments::class => new NoUnusedFragments(),
|
||||||
PossibleFragmentSpreads::class => new PossibleFragmentSpreads(),
|
PossibleFragmentSpreads::class => new PossibleFragmentSpreads(),
|
||||||
NoFragmentCycles::class => new NoFragmentCycles(),
|
NoFragmentCycles::class => new NoFragmentCycles(),
|
||||||
UniqueVariableNames::class => new UniqueVariableNames(),
|
UniqueVariableNames::class => new UniqueVariableNames(),
|
||||||
NoUndefinedVariables::class => new NoUndefinedVariables(),
|
NoUndefinedVariables::class => new NoUndefinedVariables(),
|
||||||
NoUnusedVariables::class => new NoUnusedVariables(),
|
NoUnusedVariables::class => new NoUnusedVariables(),
|
||||||
KnownDirectives::class => new KnownDirectives(),
|
KnownDirectives::class => new KnownDirectives(),
|
||||||
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
|
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
|
||||||
KnownArgumentNames::class => new KnownArgumentNames(),
|
KnownArgumentNames::class => new KnownArgumentNames(),
|
||||||
UniqueArgumentNames::class => new UniqueArgumentNames(),
|
UniqueArgumentNames::class => new UniqueArgumentNames(),
|
||||||
ValuesOfCorrectType::class => new ValuesOfCorrectType(),
|
ValuesOfCorrectType::class => new ValuesOfCorrectType(),
|
||||||
ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
|
ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
|
||||||
VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(),
|
VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(),
|
||||||
VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
|
VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
|
||||||
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
|
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
|
||||||
UniqueInputFieldNames::class => new UniqueInputFieldNames(),
|
UniqueInputFieldNames::class => new UniqueInputFieldNames(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +159,7 @@ class DocumentValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return QuerySecurityRule[]
|
||||||
*/
|
*/
|
||||||
public static function securityRules()
|
public static function securityRules()
|
||||||
{
|
{
|
||||||
@ -159,16 +167,36 @@ class DocumentValidator
|
|||||||
// When custom security rule is required - it should be just added via DocumentValidator::addRule();
|
// When custom security rule is required - it should be just added via DocumentValidator::addRule();
|
||||||
// TODO: deprecate this
|
// TODO: deprecate this
|
||||||
|
|
||||||
if (null === self::$securityRules) {
|
if (self::$securityRules === null) {
|
||||||
self::$securityRules = [
|
self::$securityRules = [
|
||||||
DisableIntrospection::class => new DisableIntrospection(DisableIntrospection::DISABLED), // DEFAULT DISABLED
|
DisableIntrospection::class => new DisableIntrospection(DisableIntrospection::DISABLED), // DEFAULT DISABLED
|
||||||
QueryDepth::class => new QueryDepth(QueryDepth::DISABLED), // default disabled
|
QueryDepth::class => new QueryDepth(QueryDepth::DISABLED), // default disabled
|
||||||
QueryComplexity::class => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
|
QueryComplexity::class => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$securityRules;
|
return self::$securityRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This uses a specialized visitor which runs multiple visitors in parallel,
|
||||||
|
* while maintaining the visitor skip and break API.
|
||||||
|
*
|
||||||
|
* @param ValidationRule[] $rules
|
||||||
|
* @return Error[]
|
||||||
|
*/
|
||||||
|
public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules)
|
||||||
|
{
|
||||||
|
$context = new ValidationContext($schema, $documentNode, $typeInfo);
|
||||||
|
$visitors = [];
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
$visitors[] = $rule->getVisitor($context);
|
||||||
|
}
|
||||||
|
Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
|
||||||
|
|
||||||
|
return $context->getErrors();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns global validation rule by name. Standard rules are named by class name, so
|
* Returns global validation rule by name. Standard rules are named by class name, so
|
||||||
* example usage for such rules:
|
* example usage for such rules:
|
||||||
@ -177,7 +205,7 @@ class DocumentValidator
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return AbstractValidationRule
|
* @return ValidationRule
|
||||||
*/
|
*/
|
||||||
public static function getRule($name)
|
public static function getRule($name)
|
||||||
{
|
{
|
||||||
@ -187,17 +215,17 @@ class DocumentValidator
|
|||||||
return $rules[$name];
|
return $rules[$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = "GraphQL\\Validator\\Rules\\$name";
|
$name = sprintf('GraphQL\\Validator\\Rules\\%s', $name);
|
||||||
return isset($rules[$name]) ? $rules[$name] : null ;
|
|
||||||
|
return $rules[$name] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add rule to list of global validation rules
|
* Add rule to list of global validation rules
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param AbstractValidationRule $rule
|
|
||||||
*/
|
*/
|
||||||
public static function addRule(AbstractValidationRule $rule)
|
public static function addRule(ValidationRule $rule)
|
||||||
{
|
{
|
||||||
self::$rules[$rule->getName()] = $rule;
|
self::$rules[$rule->getName()] = $rule;
|
||||||
}
|
}
|
||||||
@ -205,7 +233,12 @@ class DocumentValidator
|
|||||||
public static function isError($value)
|
public static function isError($value)
|
||||||
{
|
{
|
||||||
return is_array($value)
|
return is_array($value)
|
||||||
? count(array_filter($value, function($item) { return $item instanceof \Exception || $item instanceof \Throwable;})) === count($value)
|
? count(array_filter(
|
||||||
|
$value,
|
||||||
|
function ($item) {
|
||||||
|
return $item instanceof \Exception || $item instanceof \Throwable;
|
||||||
|
}
|
||||||
|
)) === count($value)
|
||||||
: ($value instanceof \Exception || $value instanceof \Throwable);
|
: ($value instanceof \Exception || $value instanceof \Throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +249,7 @@ class DocumentValidator
|
|||||||
} else {
|
} else {
|
||||||
$arr[] = $items;
|
$arr[] = $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,33 +264,13 @@ class DocumentValidator
|
|||||||
public static function isValidLiteralValue(Type $type, $valueNode)
|
public static function isValidLiteralValue(Type $type, $valueNode)
|
||||||
{
|
{
|
||||||
$emptySchema = new Schema([]);
|
$emptySchema = new Schema([]);
|
||||||
$emptyDoc = new DocumentNode(['definitions' => []]);
|
$emptyDoc = new DocumentNode(['definitions' => []]);
|
||||||
$typeInfo = new TypeInfo($emptySchema, $type);
|
$typeInfo = new TypeInfo($emptySchema, $type);
|
||||||
$context = new ValidationContext($emptySchema, $emptyDoc, $typeInfo);
|
$context = new ValidationContext($emptySchema, $emptyDoc, $typeInfo);
|
||||||
$validator = new ValuesOfCorrectType();
|
$validator = new ValuesOfCorrectType();
|
||||||
$visitor = $validator->getVisitor($context);
|
$visitor = $validator->getVisitor($context);
|
||||||
Visitor::visit($valueNode, Visitor::visitWithTypeInfo($typeInfo, $visitor));
|
Visitor::visit($valueNode, Visitor::visitWithTypeInfo($typeInfo, $visitor));
|
||||||
return $context->getErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This uses a specialized visitor which runs multiple visitors in parallel,
|
|
||||||
* while maintaining the visitor skip and break API.
|
|
||||||
*
|
|
||||||
* @param Schema $schema
|
|
||||||
* @param TypeInfo $typeInfo
|
|
||||||
* @param DocumentNode $documentNode
|
|
||||||
* @param AbstractValidationRule[] $rules
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules)
|
|
||||||
{
|
|
||||||
$context = new ValidationContext($schema, $documentNode, $typeInfo);
|
|
||||||
$visitors = [];
|
|
||||||
foreach ($rules as $rule) {
|
|
||||||
$visitors[] = $rule->getVisitor($context);
|
|
||||||
}
|
|
||||||
Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
|
|
||||||
return $context->getErrors();
|
return $context->getErrors();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
class CustomValidationRule extends AbstractValidationRule
|
class CustomValidationRule extends ValidationRule
|
||||||
{
|
{
|
||||||
|
/** @var callable */
|
||||||
private $visitorFn;
|
private $visitorFn;
|
||||||
|
|
||||||
public function __construct($name, callable $visitorFn)
|
public function __construct($name, callable $visitorFn)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->visitorFn = $visitorFn;
|
$this->visitorFn = $visitorFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ValidationContext $context
|
|
||||||
* @return Error[]
|
* @return Error[]
|
||||||
*/
|
*/
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$fn = $this->visitorFn;
|
$fn = $this->visitorFn;
|
||||||
|
|
||||||
return $fn($context);
|
return $fn($context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -6,9 +9,11 @@ use GraphQL\Language\AST\FieldNode;
|
|||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
class DisableIntrospection extends AbstractQuerySecurity
|
class DisableIntrospection extends QuerySecurityRule
|
||||||
{
|
{
|
||||||
const ENABLED = 1;
|
public const ENABLED = 1;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
private $isEnabled;
|
private $isEnabled;
|
||||||
|
|
||||||
public function __construct($enabled = self::ENABLED)
|
public function __construct($enabled = self::ENABLED)
|
||||||
@ -21,7 +26,26 @@ class DisableIntrospection extends AbstractQuerySecurity
|
|||||||
$this->isEnabled = $enabled;
|
$this->isEnabled = $enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function introspectionDisabledMessage()
|
public function getVisitor(ValidationContext $context)
|
||||||
|
{
|
||||||
|
return $this->invokeIfNeeded(
|
||||||
|
$context,
|
||||||
|
[
|
||||||
|
NodeKind::FIELD => function (FieldNode $node) use ($context) {
|
||||||
|
if ($node->name->value !== '__type' && $node->name->value !== '__schema') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
static::introspectionDisabledMessage(),
|
||||||
|
[$node]
|
||||||
|
));
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function introspectionDisabledMessage()
|
||||||
{
|
{
|
||||||
return 'GraphQL introspection is not allowed, but the query contained __schema or __type';
|
return 'GraphQL introspection is not allowed, but the query contained __schema or __type';
|
||||||
}
|
}
|
||||||
@ -30,21 +54,4 @@ class DisableIntrospection extends AbstractQuerySecurity
|
|||||||
{
|
{
|
||||||
return $this->isEnabled !== static::DISABLED;
|
return $this->isEnabled !== static::DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
|
||||||
{
|
|
||||||
return $this->invokeIfNeeded(
|
|
||||||
$context,
|
|
||||||
[
|
|
||||||
NodeKind::FIELD => function (FieldNode $node) use ($context) {
|
|
||||||
if ($node->name->value === '__type' || $node->name->value === '__schema') {
|
|
||||||
$context->reportError(new Error(
|
|
||||||
static::introspectionDisabledMessage(),
|
|
||||||
[$node]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -9,6 +12,7 @@ use GraphQL\Language\AST\NodeKind;
|
|||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executable definitions
|
* Executable definitions
|
||||||
@ -16,32 +20,33 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
* A GraphQL document is only valid for execution if all definitions are either
|
* A GraphQL document is only valid for execution if all definitions are either
|
||||||
* operation or fragment definitions.
|
* operation or fragment definitions.
|
||||||
*/
|
*/
|
||||||
class ExecutableDefinitions extends AbstractValidationRule
|
class ExecutableDefinitions extends ValidationRule
|
||||||
{
|
{
|
||||||
static function nonExecutableDefinitionMessage($defName)
|
|
||||||
{
|
|
||||||
return "The \"$defName\" definition is not executable.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::DOCUMENT => function (DocumentNode $node) use ($context) {
|
NodeKind::DOCUMENT => function (DocumentNode $node) use ($context) {
|
||||||
/** @var Node $definition */
|
/** @var Node $definition */
|
||||||
foreach ($node->definitions as $definition) {
|
foreach ($node->definitions as $definition) {
|
||||||
if (
|
if ($definition instanceof OperationDefinitionNode ||
|
||||||
!$definition instanceof OperationDefinitionNode &&
|
$definition instanceof FragmentDefinitionNode
|
||||||
!$definition instanceof FragmentDefinitionNode
|
|
||||||
) {
|
) {
|
||||||
$context->reportError(new Error(
|
continue;
|
||||||
self::nonExecutableDefinitionMessage($definition->name->value),
|
|
||||||
[$definition->name]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::nonExecutableDefinitionMessage($definition->name->value),
|
||||||
|
[$definition->name]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function nonExecutableDefinitionMessage($defName)
|
||||||
|
{
|
||||||
|
return sprintf('The "%s" definition is not executable.', $defName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -10,62 +13,56 @@ use GraphQL\Type\Definition\Type;
|
|||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function array_keys;
|
||||||
|
use function array_merge;
|
||||||
|
use function arsort;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class FieldsOnCorrectType extends AbstractValidationRule
|
class FieldsOnCorrectType extends ValidationRule
|
||||||
{
|
{
|
||||||
static function undefinedFieldMessage($fieldName, $type, array $suggestedTypeNames, array $suggestedFieldNames)
|
|
||||||
{
|
|
||||||
$message = 'Cannot query field "' . $fieldName . '" on type "' . $type.'".';
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::FIELD => function(FieldNode $node) use ($context) {
|
NodeKind::FIELD => function (FieldNode $node) use ($context) {
|
||||||
$type = $context->getParentType();
|
$type = $context->getParentType();
|
||||||
if ($type) {
|
if (! $type) {
|
||||||
$fieldDef = $context->getFieldDef();
|
return;
|
||||||
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,
|
|
||||||
$suggestedTypeNames,
|
|
||||||
$suggestedFieldNames
|
|
||||||
),
|
|
||||||
[$node]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
$fieldDef = $context->getFieldDef();
|
||||||
|
if ($fieldDef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
$suggestedTypeNames,
|
||||||
|
$suggestedFieldNames
|
||||||
|
),
|
||||||
|
[$node]
|
||||||
|
));
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,32 +72,31 @@ class FieldsOnCorrectType extends AbstractValidationRule
|
|||||||
* suggest them, sorted by how often the type is referenced, starting
|
* suggest them, sorted by how often the type is referenced, starting
|
||||||
* with Interfaces.
|
* with Interfaces.
|
||||||
*
|
*
|
||||||
* @param Schema $schema
|
* @param ObjectType|InterfaceType $type
|
||||||
* @param $type
|
* @param string $fieldName
|
||||||
* @param string $fieldName
|
* @return string[]
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
|
private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
|
||||||
{
|
{
|
||||||
if (Type::isAbstractType($type)) {
|
if (Type::isAbstractType($type)) {
|
||||||
$suggestedObjectTypes = [];
|
$suggestedObjectTypes = [];
|
||||||
$interfaceUsageCount = [];
|
$interfaceUsageCount = [];
|
||||||
|
|
||||||
foreach($schema->getPossibleTypes($type) as $possibleType) {
|
foreach ($schema->getPossibleTypes($type) as $possibleType) {
|
||||||
$fields = $possibleType->getFields();
|
$fields = $possibleType->getFields();
|
||||||
if (!isset($fields[$fieldName])) {
|
if (! isset($fields[$fieldName])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// This object type defines this field.
|
// This object type defines this field.
|
||||||
$suggestedObjectTypes[] = $possibleType->name;
|
$suggestedObjectTypes[] = $possibleType->name;
|
||||||
foreach($possibleType->getInterfaces() as $possibleInterface) {
|
foreach ($possibleType->getInterfaces() as $possibleInterface) {
|
||||||
$fields = $possibleInterface->getFields();
|
$fields = $possibleInterface->getFields();
|
||||||
if (!isset($fields[$fieldName])) {
|
if (! isset($fields[$fieldName])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// This interface type defines this field.
|
// This interface type defines this field.
|
||||||
$interfaceUsageCount[$possibleInterface->name] =
|
$interfaceUsageCount[$possibleInterface->name] =
|
||||||
!isset($interfaceUsageCount[$possibleInterface->name])
|
! isset($interfaceUsageCount[$possibleInterface->name])
|
||||||
? 0
|
? 0
|
||||||
: $interfaceUsageCount[$possibleInterface->name] + 1;
|
: $interfaceUsageCount[$possibleInterface->name] + 1;
|
||||||
}
|
}
|
||||||
@ -122,18 +118,47 @@ class FieldsOnCorrectType extends AbstractValidationRule
|
|||||||
* For the field name provided, determine if there are any similar field names
|
* For the field name provided, determine if there are any similar field names
|
||||||
* that may be the result of a typo.
|
* that may be the result of a typo.
|
||||||
*
|
*
|
||||||
* @param Schema $schema
|
* @param ObjectType|InterfaceType $type
|
||||||
* @param $type
|
* @param string $fieldName
|
||||||
* @param string $fieldName
|
|
||||||
* @return array|string[]
|
* @return array|string[]
|
||||||
*/
|
*/
|
||||||
private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
|
private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
|
||||||
{
|
{
|
||||||
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
|
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
|
||||||
$possibleFieldNames = array_keys($type->getFields());
|
$possibleFieldNames = array_keys($type->getFields());
|
||||||
|
|
||||||
return Utils::suggestionList($fieldName, $possibleFieldNames);
|
return Utils::suggestionList($fieldName, $possibleFieldNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, must be a Union type, which does not define fields.
|
// Otherwise, must be a Union type, which does not define fields.
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fieldName
|
||||||
|
* @param string $type
|
||||||
|
* @param string[] $suggestedTypeNames
|
||||||
|
* @param string[] $suggestedFieldNames
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function undefinedFieldMessage(
|
||||||
|
$fieldName,
|
||||||
|
$type,
|
||||||
|
array $suggestedTypeNames,
|
||||||
|
array $suggestedFieldNames
|
||||||
|
) {
|
||||||
|
$message = sprintf('Cannot query field "%s" on type "%s".', $fieldName, $type);
|
||||||
|
|
||||||
|
if ($suggestedTypeNames) {
|
||||||
|
$suggestions = Utils::quotedOrList($suggestedTypeNames);
|
||||||
|
|
||||||
|
$message .= sprintf(' Did you mean to use an inline fragment on %s?', $suggestions);
|
||||||
|
} elseif (! empty($suggestedFieldNames)) {
|
||||||
|
$suggestions = Utils::quotedOrList($suggestedFieldNames);
|
||||||
|
|
||||||
|
$message .= sprintf(' Did you mean %s?', $suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -9,43 +12,53 @@ use GraphQL\Language\Printer;
|
|||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class FragmentsOnCompositeTypes extends AbstractValidationRule
|
class FragmentsOnCompositeTypes extends ValidationRule
|
||||||
{
|
{
|
||||||
static function inlineFragmentOnNonCompositeErrorMessage($type)
|
|
||||||
{
|
|
||||||
return "Fragment cannot condition on non composite type \"$type\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function fragmentOnNonCompositeErrorMessage($fragName, $type)
|
|
||||||
{
|
|
||||||
return "Fragment \"$fragName\" cannot condition on non composite type \"$type\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::INLINE_FRAGMENT => function(InlineFragmentNode $node) use ($context) {
|
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
|
||||||
if ($node->typeCondition) {
|
if (! $node->typeCondition) {
|
||||||
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
|
return;
|
||||||
if ($type && !Type::isCompositeType($type)) {
|
|
||||||
$context->reportError(new Error(
|
|
||||||
static::inlineFragmentOnNonCompositeErrorMessage($type),
|
|
||||||
[$node->typeCondition]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
|
||||||
|
if (! $type || Type::isCompositeType($type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
static::inlineFragmentOnNonCompositeErrorMessage($type),
|
||||||
|
[$node->typeCondition]
|
||||||
|
));
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) use ($context) {
|
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
||||||
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
|
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
|
||||||
|
|
||||||
if ($type && !Type::isCompositeType($type)) {
|
if (! $type || Type::isCompositeType($type)) {
|
||||||
$context->reportError(new Error(
|
return;
|
||||||
static::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)),
|
|
||||||
[$node->typeCondition]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
static::fragmentOnNonCompositeErrorMessage(
|
||||||
|
$node->name->value,
|
||||||
|
Printer::doPrint($node->typeCondition)
|
||||||
|
),
|
||||||
|
[$node->typeCondition]
|
||||||
|
));
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function inlineFragmentOnNonCompositeErrorMessage($type)
|
||||||
|
{
|
||||||
|
return sprintf('Fragment cannot condition on non composite type "%s".', $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fragmentOnNonCompositeErrorMessage($fragName, $type)
|
||||||
|
{
|
||||||
|
return sprintf('Fragment "%s" cannot condition on non composite type "%s".', $fragName, $type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -6,6 +9,9 @@ use GraphQL\Language\AST\ArgumentNode;
|
|||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function array_map;
|
||||||
|
use function count;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Known argument names
|
* Known argument names
|
||||||
@ -13,68 +19,88 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
* A GraphQL field is only valid if all supplied arguments are defined by
|
* A GraphQL field is only valid if all supplied arguments are defined by
|
||||||
* that field.
|
* that field.
|
||||||
*/
|
*/
|
||||||
class KnownArgumentNames extends AbstractValidationRule
|
class KnownArgumentNames extends ValidationRule
|
||||||
{
|
{
|
||||||
public static function unknownArgMessage($argName, $fieldName, $typeName, array $suggestedArgs)
|
|
||||||
{
|
|
||||||
$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, array $suggestedArgs)
|
|
||||||
{
|
|
||||||
$message = "Unknown argument \"$argName\" on directive \"@$directiveName\".";
|
|
||||||
if ($suggestedArgs) {
|
|
||||||
$message .= ' Did you mean ' . Utils::quotedOrList($suggestedArgs) . '?';
|
|
||||||
}
|
|
||||||
return $message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::ARGUMENT => function(ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
|
NodeKind::ARGUMENT => function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
|
||||||
$argDef = $context->getArgument();
|
$argDef = $context->getArgument();
|
||||||
if (!$argDef) {
|
if ($argDef !== null) {
|
||||||
$argumentOf = $ancestors[count($ancestors) - 1];
|
return;
|
||||||
if ($argumentOf->kind === NodeKind::FIELD) {
|
}
|
||||||
$fieldDef = $context->getFieldDef();
|
|
||||||
$parentType = $context->getParentType();
|
$argumentOf = $ancestors[count($ancestors) - 1];
|
||||||
if ($fieldDef && $parentType) {
|
if ($argumentOf->kind === NodeKind::FIELD) {
|
||||||
$context->reportError(new Error(
|
$fieldDef = $context->getFieldDef();
|
||||||
self::unknownArgMessage(
|
$parentType = $context->getParentType();
|
||||||
|
if ($fieldDef && $parentType) {
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::unknownArgMessage(
|
||||||
|
$node->name->value,
|
||||||
|
$fieldDef->name,
|
||||||
|
$parentType->name,
|
||||||
|
Utils::suggestionList(
|
||||||
$node->name->value,
|
$node->name->value,
|
||||||
$fieldDef->name,
|
array_map(
|
||||||
$parentType->name,
|
function ($arg) {
|
||||||
Utils::suggestionList(
|
return $arg->name;
|
||||||
$node->name->value,
|
},
|
||||||
array_map(function ($arg) { return $arg->name; }, $fieldDef->args)
|
$fieldDef->args
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
[$node]
|
),
|
||||||
));
|
[$node]
|
||||||
}
|
));
|
||||||
} else if ($argumentOf->kind === NodeKind::DIRECTIVE) {
|
}
|
||||||
$directive = $context->getDirective();
|
} elseif ($argumentOf->kind === NodeKind::DIRECTIVE) {
|
||||||
if ($directive) {
|
$directive = $context->getDirective();
|
||||||
$context->reportError(new Error(
|
if ($directive) {
|
||||||
self::unknownDirectiveArgMessage(
|
$context->reportError(new Error(
|
||||||
|
self::unknownDirectiveArgMessage(
|
||||||
|
$node->name->value,
|
||||||
|
$directive->name,
|
||||||
|
Utils::suggestionList(
|
||||||
$node->name->value,
|
$node->name->value,
|
||||||
$directive->name,
|
array_map(
|
||||||
Utils::suggestionList(
|
function ($arg) {
|
||||||
$node->name->value,
|
return $arg->name;
|
||||||
array_map(function ($arg) { return $arg->name; }, $directive->args)
|
},
|
||||||
|
$directive->args
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
[$node]
|
),
|
||||||
));
|
[$node]
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $suggestedArgs
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
$message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -7,19 +10,12 @@ use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
|||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\DirectiveLocation;
|
use GraphQL\Language\DirectiveLocation;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function count;
|
||||||
|
use function in_array;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class KnownDirectives extends AbstractValidationRule
|
class KnownDirectives extends ValidationRule
|
||||||
{
|
{
|
||||||
static function unknownDirectiveMessage($directiveName)
|
|
||||||
{
|
|
||||||
return "Unknown directive \"$directiveName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function misplacedDirectiveMessage($directiveName, $location)
|
|
||||||
{
|
|
||||||
return "Directive \"$directiveName\" may not be used on \"$location\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -32,39 +28,51 @@ class KnownDirectives extends AbstractValidationRule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$directiveDef) {
|
if (! $directiveDef) {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::unknownDirectiveMessage($node->name->value),
|
self::unknownDirectiveMessage($node->name->value),
|
||||||
[$node]
|
[$node]
|
||||||
));
|
));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
|
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
|
||||||
|
|
||||||
if (!$candidateLocation) {
|
if (! $candidateLocation) {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::misplacedDirectiveMessage($node->name->value, $node->type),
|
self::misplacedDirectiveMessage($node->name->value, $node->type),
|
||||||
[$node]
|
[$node]
|
||||||
));
|
));
|
||||||
} else if (!in_array($candidateLocation, $directiveDef->locations)) {
|
} elseif (! in_array($candidateLocation, $directiveDef->locations)) {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::misplacedDirectiveMessage($node->name->value, $candidateLocation),
|
self::misplacedDirectiveMessage($node->name->value, $candidateLocation),
|
||||||
[ $node ]
|
[$node]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function unknownDirectiveMessage($directiveName)
|
||||||
|
{
|
||||||
|
return sprintf('Unknown directive "%s".', $directiveName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param (Node|NodeList)[] $ancestors
|
||||||
|
*/
|
||||||
private function getDirectiveLocationForASTPath(array $ancestors)
|
private function getDirectiveLocationForASTPath(array $ancestors)
|
||||||
{
|
{
|
||||||
$appliedTo = $ancestors[count($ancestors) - 1];
|
$appliedTo = $ancestors[count($ancestors) - 1];
|
||||||
switch ($appliedTo->kind) {
|
switch ($appliedTo->kind) {
|
||||||
case NodeKind::OPERATION_DEFINITION:
|
case NodeKind::OPERATION_DEFINITION:
|
||||||
switch ($appliedTo->operation) {
|
switch ($appliedTo->operation) {
|
||||||
case 'query': return DirectiveLocation::QUERY;
|
case 'query':
|
||||||
case 'mutation': return DirectiveLocation::MUTATION;
|
return DirectiveLocation::QUERY;
|
||||||
case 'subscription': return DirectiveLocation::SUBSCRIPTION;
|
case 'mutation':
|
||||||
|
return DirectiveLocation::MUTATION;
|
||||||
|
case 'subscription':
|
||||||
|
return DirectiveLocation::SUBSCRIPTION;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NodeKind::FIELD:
|
case NodeKind::FIELD:
|
||||||
@ -101,9 +109,15 @@ class KnownDirectives extends AbstractValidationRule
|
|||||||
return DirectiveLocation::INPUT_OBJECT;
|
return DirectiveLocation::INPUT_OBJECT;
|
||||||
case NodeKind::INPUT_VALUE_DEFINITION:
|
case NodeKind::INPUT_VALUE_DEFINITION:
|
||||||
$parentNode = $ancestors[count($ancestors) - 3];
|
$parentNode = $ancestors[count($ancestors) - 3];
|
||||||
|
|
||||||
return $parentNode instanceof InputObjectTypeDefinitionNode
|
return $parentNode instanceof InputObjectTypeDefinitionNode
|
||||||
? DirectiveLocation::INPUT_FIELD_DEFINITION
|
? DirectiveLocation::INPUT_FIELD_DEFINITION
|
||||||
: DirectiveLocation::ARGUMENT_DEFINITION;
|
: DirectiveLocation::ARGUMENT_DEFINITION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function misplacedDirectiveMessage($directiveName, $location)
|
||||||
|
{
|
||||||
|
return sprintf('Directive "%s" may not be used on "%s".', $directiveName, $location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,40 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class KnownFragmentNames extends AbstractValidationRule
|
class KnownFragmentNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function unknownFragmentMessage($fragName)
|
|
||||||
{
|
|
||||||
return "Unknown fragment \"$fragName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::FRAGMENT_SPREAD => function(FragmentSpreadNode $node) use ($context) {
|
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
|
||||||
$fragmentName = $node->name->value;
|
$fragmentName = $node->name->value;
|
||||||
$fragment = $context->getFragment($fragmentName);
|
$fragment = $context->getFragment($fragmentName);
|
||||||
if (!$fragment) {
|
if ($fragment) {
|
||||||
$context->reportError(new Error(
|
return;
|
||||||
self::unknownFragmentMessage($fragmentName),
|
|
||||||
[$node->name]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::unknownFragmentMessage($fragmentName),
|
||||||
|
[$node->name]
|
||||||
|
));
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fragName
|
||||||
|
*/
|
||||||
|
public static function unknownFragmentMessage($fragName)
|
||||||
|
{
|
||||||
|
return sprintf('Unknown fragment "%s".', $fragName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -7,6 +10,8 @@ use GraphQL\Language\AST\NodeKind;
|
|||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function array_keys;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Known type names
|
* Known type names
|
||||||
@ -14,43 +19,54 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
* A GraphQL document is only valid if referenced types (specifically
|
* A GraphQL document is only valid if referenced types (specifically
|
||||||
* variable definitions and fragment conditions) are defined by the type schema.
|
* variable definitions and fragment conditions) are defined by the type schema.
|
||||||
*/
|
*/
|
||||||
class KnownTypeNames extends AbstractValidationRule
|
class KnownTypeNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function unknownTypeMessage($type, array $suggestedTypes)
|
|
||||||
{
|
|
||||||
$message = "Unknown type \"$type\".";
|
|
||||||
if ($suggestedTypes) {
|
|
||||||
$suggestions = Utils::quotedOrList($suggestedTypes);
|
|
||||||
$message .= " Did you mean $suggestions?";
|
|
||||||
}
|
|
||||||
return $message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$skip = function() { return Visitor::skipNode(); };
|
$skip = function () {
|
||||||
|
return Visitor::skipNode();
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// TODO: when validating IDL, re-enable these. Experimental version does not
|
// TODO: when validating IDL, re-enable these. Experimental version does not
|
||||||
// add unreferenced types, resulting in false-positive errors. Squelched
|
// add unreferenced types, resulting in false-positive errors. Squelched
|
||||||
// errors for now.
|
// errors for now.
|
||||||
NodeKind::OBJECT_TYPE_DEFINITION => $skip,
|
NodeKind::OBJECT_TYPE_DEFINITION => $skip,
|
||||||
NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
|
NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
|
||||||
NodeKind::UNION_TYPE_DEFINITION => $skip,
|
NodeKind::UNION_TYPE_DEFINITION => $skip,
|
||||||
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip,
|
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip,
|
||||||
NodeKind::NAMED_TYPE => function(NamedTypeNode $node) use ($context) {
|
NodeKind::NAMED_TYPE => function (NamedTypeNode $node) use ($context) {
|
||||||
$schema = $context->getSchema();
|
$schema = $context->getSchema();
|
||||||
$typeName = $node->name->value;
|
$typeName = $node->name->value;
|
||||||
$type = $schema->getType($typeName);
|
$type = $schema->getType($typeName);
|
||||||
if (!$type) {
|
if ($type !== null) {
|
||||||
$context->reportError(new Error(
|
return;
|
||||||
self::unknownTypeMessage(
|
|
||||||
$typeName,
|
|
||||||
Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
|
|
||||||
), [$node])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::unknownTypeMessage(
|
||||||
|
$typeName,
|
||||||
|
Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
|
||||||
|
),
|
||||||
|
[$node]
|
||||||
|
));
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $type
|
||||||
|
* @param string[] $suggestedTypes
|
||||||
|
*/
|
||||||
|
public static function unknownTypeMessage($type, array $suggestedTypes)
|
||||||
|
{
|
||||||
|
$message = sprintf('Unknown type "%s".', $type);
|
||||||
|
if (! empty($suggestedTypes)) {
|
||||||
|
$suggestions = Utils::quotedOrList($suggestedTypes);
|
||||||
|
|
||||||
|
$message .= sprintf(' Did you mean %s?', $suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -7,6 +10,7 @@ use GraphQL\Language\AST\NodeKind;
|
|||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function count;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lone anonymous operation
|
* Lone anonymous operation
|
||||||
@ -14,33 +18,40 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
* A GraphQL document is only valid if when it contains an anonymous operation
|
* A GraphQL document is only valid if when it contains an anonymous operation
|
||||||
* (the query short-hand) that it contains only that one operation definition.
|
* (the query short-hand) that it contains only that one operation definition.
|
||||||
*/
|
*/
|
||||||
class LoneAnonymousOperation extends AbstractValidationRule
|
class LoneAnonymousOperation extends ValidationRule
|
||||||
{
|
{
|
||||||
static function anonOperationNotAloneMessage()
|
|
||||||
{
|
|
||||||
return 'This anonymous operation must be the only defined operation.';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$operationCount = 0;
|
$operationCount = 0;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::DOCUMENT => function(DocumentNode $node) use (&$operationCount) {
|
NodeKind::DOCUMENT => function (DocumentNode $node) use (&$operationCount) {
|
||||||
$tmp = Utils::filter(
|
$tmp = Utils::filter(
|
||||||
$node->definitions,
|
$node->definitions,
|
||||||
function ($definition) {
|
function ($definition) {
|
||||||
return $definition->kind === NodeKind::OPERATION_DEFINITION;
|
return $definition->kind === NodeKind::OPERATION_DEFINITION;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$operationCount = count($tmp);
|
$operationCount = count($tmp);
|
||||||
},
|
},
|
||||||
NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) use (&$operationCount, $context) {
|
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use (
|
||||||
if (!$node->name && $operationCount > 1) {
|
&$operationCount,
|
||||||
$context->reportError(
|
$context
|
||||||
new Error(self::anonOperationNotAloneMessage(), [$node])
|
) {
|
||||||
);
|
if ($node->name || $operationCount <= 1) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
$context->reportError(
|
||||||
|
new Error(self::anonOperationNotAloneMessage(), [$node])
|
||||||
|
);
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function anonOperationNotAloneMessage()
|
||||||
|
{
|
||||||
|
return 'This anonymous operation must be the only defined operation.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,33 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\ValidationContext;
|
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 AbstractValidationRule
|
class NoFragmentCycles extends ValidationRule
|
||||||
{
|
{
|
||||||
static function cycleErrorMessage($fragName, array $spreadNames = [])
|
/** @var bool[] */
|
||||||
{
|
|
||||||
$via = !empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : '';
|
|
||||||
return "Cannot spread fragment \"$fragName\" within itself$via.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $visitedFrags;
|
public $visitedFrags;
|
||||||
|
|
||||||
|
/** @var FragmentSpreadNode[] */
|
||||||
public $spreadPath;
|
public $spreadPath;
|
||||||
|
|
||||||
|
/** @var (int|null)[] */
|
||||||
public $spreadPathIndexByName;
|
public $spreadPathIndexByName;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -38,18 +46,19 @@ class NoFragmentCycles extends AbstractValidationRule
|
|||||||
NodeKind::OPERATION_DEFINITION => function () {
|
NodeKind::OPERATION_DEFINITION => function () {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
||||||
if (!isset($this->visitedFrags[$node->name->value])) {
|
if (! isset($this->visitedFrags[$node->name->value])) {
|
||||||
$this->detectCycleRecursive($node, $context);
|
$this->detectCycleRecursive($node, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
|
private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
|
||||||
{
|
{
|
||||||
$fragmentName = $fragment->name->value;
|
$fragmentName = $fragment->name->value;
|
||||||
$this->visitedFrags[$fragmentName] = true;
|
$this->visitedFrags[$fragmentName] = true;
|
||||||
|
|
||||||
$spreadNodes = $context->getFragmentSpreads($fragment);
|
$spreadNodes = $context->getFragmentSpreads($fragment);
|
||||||
@ -63,7 +72,7 @@ class NoFragmentCycles extends AbstractValidationRule
|
|||||||
for ($i = 0; $i < count($spreadNodes); $i++) {
|
for ($i = 0; $i < count($spreadNodes); $i++) {
|
||||||
$spreadNode = $spreadNodes[$i];
|
$spreadNode = $spreadNodes[$i];
|
||||||
$spreadName = $spreadNode->name->value;
|
$spreadName = $spreadNode->name->value;
|
||||||
$cycleIndex = isset($this->spreadPathIndexByName[$spreadName]) ? $this->spreadPathIndexByName[$spreadName] : null;
|
$cycleIndex = $this->spreadPathIndexByName[$spreadName] ?? null;
|
||||||
|
|
||||||
if ($cycleIndex === null) {
|
if ($cycleIndex === null) {
|
||||||
$this->spreadPath[] = $spreadNode;
|
$this->spreadPath[] = $spreadNode;
|
||||||
@ -76,7 +85,7 @@ class NoFragmentCycles extends AbstractValidationRule
|
|||||||
array_pop($this->spreadPath);
|
array_pop($this->spreadPath);
|
||||||
} else {
|
} else {
|
||||||
$cyclePath = array_slice($this->spreadPath, $cycleIndex);
|
$cyclePath = array_slice($this->spreadPath, $cycleIndex);
|
||||||
$nodes = $cyclePath;
|
$nodes = $cyclePath;
|
||||||
|
|
||||||
if (is_array($spreadNode)) {
|
if (is_array($spreadNode)) {
|
||||||
$nodes = array_merge($nodes, $spreadNode);
|
$nodes = array_merge($nodes, $spreadNode);
|
||||||
@ -87,9 +96,12 @@ class NoFragmentCycles extends AbstractValidationRule
|
|||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::cycleErrorMessage(
|
self::cycleErrorMessage(
|
||||||
$spreadName,
|
$spreadName,
|
||||||
Utils::map($cyclePath, function ($s) {
|
Utils::map(
|
||||||
return $s->name->value;
|
$cyclePath,
|
||||||
})
|
function ($s) {
|
||||||
|
return $s->name->value;
|
||||||
|
}
|
||||||
|
)
|
||||||
),
|
),
|
||||||
$nodes
|
$nodes
|
||||||
));
|
));
|
||||||
@ -98,4 +110,16 @@ class NoFragmentCycles extends AbstractValidationRule
|
|||||||
|
|
||||||
$this->spreadPathIndexByName[$fragmentName] = null;
|
$this->spreadPathIndexByName[$fragmentName] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $spreadNames
|
||||||
|
*/
|
||||||
|
public static function cycleErrorMessage($fragName, array $spreadNames = [])
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'Cannot spread fragment "%s" within itself%s.',
|
||||||
|
$fragName,
|
||||||
|
! empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : ''
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -6,55 +9,56 @@ use GraphQL\Language\AST\NodeKind;
|
|||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class NoUndefinedVariables
|
* Class NoUndefinedVariables
|
||||||
*
|
*
|
||||||
* A GraphQL operation is only valid if all variables encountered, both directly
|
* A GraphQL operation is only valid if all variables encountered, both directly
|
||||||
* and via fragment spreads, are defined by that operation.
|
* and via fragment spreads, are defined by that operation.
|
||||||
*
|
|
||||||
* @package GraphQL\Validator\Rules
|
|
||||||
*/
|
*/
|
||||||
class NoUndefinedVariables extends AbstractValidationRule
|
class NoUndefinedVariables extends ValidationRule
|
||||||
{
|
{
|
||||||
static function undefinedVarMessage($varName, $opName = null)
|
|
||||||
{
|
|
||||||
return $opName
|
|
||||||
? "Variable \"$$varName\" is not defined by operation \"$opName\"."
|
|
||||||
: "Variable \"$$varName\" is not defined.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$variableNameDefined = [];
|
$variableNameDefined = [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::OPERATION_DEFINITION => [
|
NodeKind::OPERATION_DEFINITION => [
|
||||||
'enter' => function() use (&$variableNameDefined) {
|
'enter' => function () use (&$variableNameDefined) {
|
||||||
$variableNameDefined = [];
|
$variableNameDefined = [];
|
||||||
},
|
},
|
||||||
'leave' => function(OperationDefinitionNode $operation) use (&$variableNameDefined, $context) {
|
'leave' => function (OperationDefinitionNode $operation) use (&$variableNameDefined, $context) {
|
||||||
$usages = $context->getRecursiveVariableUsages($operation);
|
$usages = $context->getRecursiveVariableUsages($operation);
|
||||||
|
|
||||||
foreach ($usages as $usage) {
|
foreach ($usages as $usage) {
|
||||||
$node = $usage['node'];
|
$node = $usage['node'];
|
||||||
$varName = $node->name->value;
|
$varName = $node->name->value;
|
||||||
|
|
||||||
if (empty($variableNameDefined[$varName])) {
|
if (! empty($variableNameDefined[$varName])) {
|
||||||
$context->reportError(new Error(
|
continue;
|
||||||
self::undefinedVarMessage(
|
|
||||||
$varName,
|
|
||||||
$operation->name ? $operation->name->value : null
|
|
||||||
),
|
|
||||||
[ $node, $operation ]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::undefinedVarMessage(
|
||||||
|
$varName,
|
||||||
|
$operation->name ? $operation->name->value : null
|
||||||
|
),
|
||||||
|
[$node, $operation]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $def) use (&$variableNameDefined) {
|
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $def) use (&$variableNameDefined) {
|
||||||
$variableNameDefined[$def->variable->name->value] = true;
|
$variableNameDefined[$def->variable->name->value] = true;
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function undefinedVarMessage($varName, $opName = null)
|
||||||
|
{
|
||||||
|
return $opName
|
||||||
|
? sprintf('Variable "$%s" is not defined by operation "%s".', $varName, $opName)
|
||||||
|
: sprintf('Variable "$%s" is not defined.', $varName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class NoUnusedFragments extends AbstractValidationRule
|
class NoUnusedFragments extends ValidationRule
|
||||||
{
|
{
|
||||||
static function unusedFragMessage($fragName)
|
/** @var OperationDefinitionNode[] */
|
||||||
{
|
|
||||||
return "Fragment \"$fragName\" is never used.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $operationDefs;
|
public $operationDefs;
|
||||||
|
|
||||||
|
/** @var FragmentDefinitionNode[] */
|
||||||
public $fragmentDefs;
|
public $fragmentDefs;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$this->operationDefs = [];
|
$this->operationDefs = [];
|
||||||
$this->fragmentDefs = [];
|
$this->fragmentDefs = [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::OPERATION_DEFINITION => function($node) {
|
NodeKind::OPERATION_DEFINITION => function ($node) {
|
||||||
$this->operationDefs[] = $node;
|
$this->operationDefs[] = $node;
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $def) {
|
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) {
|
||||||
$this->fragmentDefs[] = $def;
|
$this->fragmentDefs[] = $def;
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::DOCUMENT => [
|
NodeKind::DOCUMENT => [
|
||||||
'leave' => function() use ($context) {
|
'leave' => function () use ($context) {
|
||||||
$fragmentNameUsed = [];
|
$fragmentNameUsed = [];
|
||||||
|
|
||||||
foreach ($this->operationDefs as $operation) {
|
foreach ($this->operationDefs as $operation) {
|
||||||
@ -44,15 +48,22 @@ class NoUnusedFragments extends AbstractValidationRule
|
|||||||
|
|
||||||
foreach ($this->fragmentDefs as $fragmentDef) {
|
foreach ($this->fragmentDefs as $fragmentDef) {
|
||||||
$fragName = $fragmentDef->name->value;
|
$fragName = $fragmentDef->name->value;
|
||||||
if (empty($fragmentNameUsed[$fragName])) {
|
if (! empty($fragmentNameUsed[$fragName])) {
|
||||||
$context->reportError(new Error(
|
continue;
|
||||||
self::unusedFragMessage($fragName),
|
|
||||||
[ $fragmentDef ]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::unusedFragMessage($fragName),
|
||||||
|
[$fragmentDef]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function unusedFragMessage($fragName)
|
||||||
|
{
|
||||||
|
return sprintf('Fragment "%s" is never used.', $fragName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class NoUnusedVariables extends AbstractValidationRule
|
class NoUnusedVariables extends ValidationRule
|
||||||
{
|
{
|
||||||
static function unusedVariableMessage($varName, $opName = null)
|
/** @var VariableDefinitionNode[] */
|
||||||
{
|
|
||||||
return $opName
|
|
||||||
? "Variable \"$$varName\" is never used in operation \"$opName\"."
|
|
||||||
: "Variable \"$$varName\" is never used.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $variableDefs;
|
public $variableDefs;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -23,34 +22,43 @@ class NoUnusedVariables extends AbstractValidationRule
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::OPERATION_DEFINITION => [
|
NodeKind::OPERATION_DEFINITION => [
|
||||||
'enter' => function() {
|
'enter' => function () {
|
||||||
$this->variableDefs = [];
|
$this->variableDefs = [];
|
||||||
},
|
},
|
||||||
'leave' => function(OperationDefinitionNode $operation) use ($context) {
|
'leave' => function (OperationDefinitionNode $operation) use ($context) {
|
||||||
$variableNameUsed = [];
|
$variableNameUsed = [];
|
||||||
$usages = $context->getRecursiveVariableUsages($operation);
|
$usages = $context->getRecursiveVariableUsages($operation);
|
||||||
$opName = $operation->name ? $operation->name->value : null;
|
$opName = $operation->name ? $operation->name->value : null;
|
||||||
|
|
||||||
foreach ($usages as $usage) {
|
foreach ($usages as $usage) {
|
||||||
$node = $usage['node'];
|
$node = $usage['node'];
|
||||||
$variableNameUsed[$node->name->value] = true;
|
$variableNameUsed[$node->name->value] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->variableDefs as $variableDef) {
|
foreach ($this->variableDefs as $variableDef) {
|
||||||
$variableName = $variableDef->variable->name->value;
|
$variableName = $variableDef->variable->name->value;
|
||||||
|
|
||||||
if (empty($variableNameUsed[$variableName])) {
|
if (! empty($variableNameUsed[$variableName])) {
|
||||||
$context->reportError(new Error(
|
continue;
|
||||||
self::unusedVariableMessage($variableName, $opName),
|
|
||||||
[$variableDef]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::unusedVariableMessage($variableName, $opName),
|
||||||
|
[$variableDef]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
NodeKind::VARIABLE_DEFINITION => function($def) {
|
NodeKind::VARIABLE_DEFINITION => function ($def) {
|
||||||
$this->variableDefs[] = $def;
|
$this->variableDefs[] = $def;
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function unusedVariableMessage($varName, $opName = null)
|
||||||
|
{
|
||||||
|
return $opName
|
||||||
|
? sprintf('Variable "$%s" is never used in operation "%s".', $varName, $opName)
|
||||||
|
: sprintf('Variable "$%s" is never used.', $varName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,72 +1,61 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\InlineFragmentNode;
|
use GraphQL\Language\AST\InlineFragmentNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
use GraphQL\Type\Definition\AbstractType;
|
use GraphQL\Type\Definition\AbstractType;
|
||||||
use GraphQL\Type\Definition\CompositeType;
|
use GraphQL\Type\Definition\CompositeType;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class PossibleFragmentSpreads extends AbstractValidationRule
|
class PossibleFragmentSpreads extends ValidationRule
|
||||||
{
|
{
|
||||||
static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
|
|
||||||
{
|
|
||||||
return "Fragment \"$fragName\" cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
|
|
||||||
{
|
|
||||||
return "Fragment cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::INLINE_FRAGMENT => function(InlineFragmentNode $node) use ($context) {
|
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
|
||||||
$fragType = $context->getType();
|
$fragType = $context->getType();
|
||||||
$parentType = $context->getParentType();
|
$parentType = $context->getParentType();
|
||||||
|
|
||||||
if ($fragType instanceof CompositeType &&
|
if (! ($fragType instanceof CompositeType) ||
|
||||||
$parentType instanceof CompositeType &&
|
! ($parentType instanceof CompositeType) ||
|
||||||
!$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
|
$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
|
||||||
$context->reportError(new Error(
|
return;
|
||||||
self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
|
||||||
[$node]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
||||||
|
[$node]
|
||||||
|
));
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_SPREAD => function(FragmentSpreadNode $node) use ($context) {
|
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
|
||||||
$fragName = $node->name->value;
|
$fragName = $node->name->value;
|
||||||
$fragType = $this->getFragmentType($context, $fragName);
|
$fragType = $this->getFragmentType($context, $fragName);
|
||||||
$parentType = $context->getParentType();
|
$parentType = $context->getParentType();
|
||||||
|
|
||||||
if ($fragType && $parentType && !$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
|
if (! $fragType ||
|
||||||
$context->reportError(new Error(
|
! $parentType ||
|
||||||
self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)
|
||||||
[$node]
|
) {
|
||||||
));
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getFragmentType(ValidationContext $context, $name)
|
$context->reportError(new Error(
|
||||||
{
|
self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
||||||
$frag = $context->getFragment($name);
|
[$node]
|
||||||
if ($frag) {
|
));
|
||||||
$type = TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition);
|
},
|
||||||
if ($type instanceof CompositeType) {
|
];
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
|
private function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
|
||||||
@ -136,4 +125,36 @@ class PossibleFragmentSpreads extends AbstractValidationRule
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'Fragment cannot be spread here as objects of type "%s" can never be of type "%s".',
|
||||||
|
$parentType,
|
||||||
|
$fragType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFragmentType(ValidationContext $context, $name)
|
||||||
|
{
|
||||||
|
$frag = $context->getFragment($name);
|
||||||
|
if ($frag) {
|
||||||
|
$type = TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition);
|
||||||
|
if ($type instanceof CompositeType) {
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'Fragment "%s" cannot be spread here as objects of type "%s" can never be of type "%s".',
|
||||||
|
$fragName,
|
||||||
|
$parentType,
|
||||||
|
$fragType
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -8,27 +11,18 @@ use GraphQL\Language\AST\NodeKind;
|
|||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Type\Definition\NonNull;
|
use GraphQL\Type\Definition\NonNull;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class ProvidedNonNullArguments extends AbstractValidationRule
|
class ProvidedNonNullArguments extends ValidationRule
|
||||||
{
|
{
|
||||||
static function missingFieldArgMessage($fieldName, $argName, $type)
|
|
||||||
{
|
|
||||||
return "Field \"$fieldName\" argument \"$argName\" of type \"$type\" is required but not provided.";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function missingDirectiveArgMessage($directiveName, $argName, $type)
|
|
||||||
{
|
|
||||||
return "Directive \"@$directiveName\" argument \"$argName\" of type \"$type\" is required but not provided.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::FIELD => [
|
NodeKind::FIELD => [
|
||||||
'leave' => function(FieldNode $fieldNode) use ($context) {
|
'leave' => function (FieldNode $fieldNode) use ($context) {
|
||||||
$fieldDef = $context->getFieldDef();
|
$fieldDef = $context->getFieldDef();
|
||||||
|
|
||||||
if (!$fieldDef) {
|
if (! $fieldDef) {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
}
|
||||||
$argNodes = $fieldNode->arguments ?: [];
|
$argNodes = $fieldNode->arguments ?: [];
|
||||||
@ -38,39 +32,67 @@ class ProvidedNonNullArguments extends AbstractValidationRule
|
|||||||
$argNodeMap[$argNode->name->value] = $argNodes;
|
$argNodeMap[$argNode->name->value] = $argNodes;
|
||||||
}
|
}
|
||||||
foreach ($fieldDef->args as $argDef) {
|
foreach ($fieldDef->args as $argDef) {
|
||||||
$argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
|
$argNode = $argNodeMap[$argDef->name] ?? null;
|
||||||
if (!$argNode && $argDef->getType() instanceof NonNull) {
|
if ($argNode || ! ($argDef->getType() instanceof NonNull)) {
|
||||||
$context->reportError(new Error(
|
continue;
|
||||||
self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
|
|
||||||
[$fieldNode]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
|
||||||
|
[$fieldNode]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
NodeKind::DIRECTIVE => [
|
NodeKind::DIRECTIVE => [
|
||||||
'leave' => function(DirectiveNode $directiveNode) use ($context) {
|
'leave' => function (DirectiveNode $directiveNode) use ($context) {
|
||||||
$directiveDef = $context->getDirective();
|
$directiveDef = $context->getDirective();
|
||||||
if (!$directiveDef) {
|
if (! $directiveDef) {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
}
|
||||||
$argNodes = $directiveNode->arguments ?: [];
|
$argNodes = $directiveNode->arguments ?: [];
|
||||||
$argNodeMap = [];
|
$argNodeMap = [];
|
||||||
foreach ($argNodes as $argNode) {
|
foreach ($argNodes as $argNode) {
|
||||||
$argNodeMap[$argNode->name->value] = $argNodes;
|
$argNodeMap[$argNode->name->value] = $argNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($directiveDef->args as $argDef) {
|
foreach ($directiveDef->args as $argDef) {
|
||||||
$argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
|
$argNode = $argNodeMap[$argDef->name] ?? null;
|
||||||
if (!$argNode && $argDef->getType() instanceof NonNull) {
|
if ($argNode || ! ($argDef->getType() instanceof NonNull)) {
|
||||||
$context->reportError(new Error(
|
continue;
|
||||||
self::missingDirectiveArgMessage($directiveNode->name->value, $argDef->name, $argDef->getType()),
|
|
||||||
[$directiveNode]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -14,20 +17,27 @@ use GraphQL\Language\Visitor;
|
|||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Type\Definition\FieldDefinition;
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function array_map;
|
||||||
|
use function call_user_func_array;
|
||||||
|
use function implode;
|
||||||
|
use function method_exists;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class QueryComplexity extends AbstractQuerySecurity
|
class QueryComplexity extends QuerySecurityRule
|
||||||
{
|
{
|
||||||
|
/** @var int */
|
||||||
private $maxQueryComplexity;
|
private $maxQueryComplexity;
|
||||||
|
|
||||||
|
/** @var mixed[]|null */
|
||||||
private $rawVariableValues = [];
|
private $rawVariableValues = [];
|
||||||
|
|
||||||
|
/** @var \ArrayObject */
|
||||||
private $variableDefs;
|
private $variableDefs;
|
||||||
|
|
||||||
|
/** @var \ArrayObject */
|
||||||
private $fieldNodeAndDefs;
|
private $fieldNodeAndDefs;
|
||||||
|
|
||||||
/**
|
/** @var ValidationContext */
|
||||||
* @var ValidationContext
|
|
||||||
*/
|
|
||||||
private $context;
|
private $context;
|
||||||
|
|
||||||
public function __construct($maxQueryComplexity)
|
public function __construct($maxQueryComplexity)
|
||||||
@ -35,50 +45,18 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
$this->setMaxQueryComplexity($maxQueryComplexity);
|
$this->setMaxQueryComplexity($maxQueryComplexity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function maxQueryComplexityErrorMessage($max, $count)
|
|
||||||
{
|
|
||||||
return sprintf('Max query complexity should be %d but got %d.', $max, $count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set max query complexity. If equal to 0 no check is done. Must be greater or equal to 0.
|
|
||||||
*
|
|
||||||
* @param $maxQueryComplexity
|
|
||||||
*/
|
|
||||||
public function setMaxQueryComplexity($maxQueryComplexity)
|
|
||||||
{
|
|
||||||
$this->checkIfGreaterOrEqualToZero('maxQueryComplexity', $maxQueryComplexity);
|
|
||||||
|
|
||||||
$this->maxQueryComplexity = (int) $maxQueryComplexity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMaxQueryComplexity()
|
|
||||||
{
|
|
||||||
return $this->maxQueryComplexity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setRawVariableValues(array $rawVariableValues = null)
|
|
||||||
{
|
|
||||||
$this->rawVariableValues = $rawVariableValues ?: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRawVariableValues()
|
|
||||||
{
|
|
||||||
return $this->rawVariableValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$this->context = $context;
|
$this->context = $context;
|
||||||
|
|
||||||
$this->variableDefs = new \ArrayObject();
|
$this->variableDefs = new \ArrayObject();
|
||||||
$this->fieldNodeAndDefs = new \ArrayObject();
|
$this->fieldNodeAndDefs = new \ArrayObject();
|
||||||
$complexity = 0;
|
$complexity = 0;
|
||||||
|
|
||||||
return $this->invokeIfNeeded(
|
return $this->invokeIfNeeded(
|
||||||
$context,
|
$context,
|
||||||
[
|
[
|
||||||
NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
|
NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
|
||||||
$this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs(
|
$this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs(
|
||||||
$context,
|
$context,
|
||||||
$context->getParentType(),
|
$context->getParentType(),
|
||||||
@ -87,23 +65,31 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
$this->fieldNodeAndDefs
|
$this->fieldNodeAndDefs
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
NodeKind::VARIABLE_DEFINITION => function ($def) {
|
NodeKind::VARIABLE_DEFINITION => function ($def) {
|
||||||
$this->variableDefs[] = $def;
|
$this->variableDefs[] = $def;
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::OPERATION_DEFINITION => [
|
NodeKind::OPERATION_DEFINITION => [
|
||||||
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) {
|
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) {
|
||||||
$errors = $context->getErrors();
|
$errors = $context->getErrors();
|
||||||
|
|
||||||
if (empty($errors)) {
|
if (! empty($errors)) {
|
||||||
$complexity = $this->fieldComplexity($operationDefinition, $complexity);
|
return;
|
||||||
|
|
||||||
if ($complexity > $this->getMaxQueryComplexity()) {
|
|
||||||
$context->reportError(
|
|
||||||
new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$complexity = $this->fieldComplexity($operationDefinition, $complexity);
|
||||||
|
|
||||||
|
if ($complexity <= $this->getMaxQueryComplexity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->reportError(
|
||||||
|
new Error($this->maxQueryComplexityErrorMessage(
|
||||||
|
$this->getMaxQueryComplexity(),
|
||||||
|
$complexity
|
||||||
|
))
|
||||||
|
);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
@ -125,9 +111,9 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
{
|
{
|
||||||
switch ($node->kind) {
|
switch ($node->kind) {
|
||||||
case NodeKind::FIELD:
|
case NodeKind::FIELD:
|
||||||
/* @var FieldNode $node */
|
/** @var FieldNode $node */
|
||||||
// default values
|
// default values
|
||||||
$args = [];
|
$args = [];
|
||||||
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
|
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
|
||||||
|
|
||||||
// calculate children complexity if needed
|
// calculate children complexity if needed
|
||||||
@ -139,7 +125,7 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
}
|
}
|
||||||
|
|
||||||
$astFieldInfo = $this->astFieldInfo($node);
|
$astFieldInfo = $this->astFieldInfo($node);
|
||||||
$fieldDef = $astFieldInfo[1];
|
$fieldDef = $astFieldInfo[1];
|
||||||
|
|
||||||
if ($fieldDef instanceof FieldDefinition) {
|
if ($fieldDef instanceof FieldDefinition) {
|
||||||
if ($this->directiveExcludesField($node)) {
|
if ($this->directiveExcludesField($node)) {
|
||||||
@ -157,7 +143,7 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeKind::INLINE_FRAGMENT:
|
case NodeKind::INLINE_FRAGMENT:
|
||||||
/* @var InlineFragmentNode $node */
|
/** @var InlineFragmentNode $node */
|
||||||
// node has children?
|
// node has children?
|
||||||
if (isset($node->selectionSet)) {
|
if (isset($node->selectionSet)) {
|
||||||
$complexity = $this->fieldComplexity($node, $complexity);
|
$complexity = $this->fieldComplexity($node, $complexity);
|
||||||
@ -165,10 +151,10 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeKind::FRAGMENT_SPREAD:
|
case NodeKind::FRAGMENT_SPREAD:
|
||||||
/* @var FragmentSpreadNode $node */
|
/** @var FragmentSpreadNode $node */
|
||||||
$fragment = $this->getFragment($node);
|
$fragment = $this->getFragment($node);
|
||||||
|
|
||||||
if (null !== $fragment) {
|
if ($fragment !== null) {
|
||||||
$complexity = $this->fieldComplexity($fragment, $complexity);
|
$complexity = $this->fieldComplexity($fragment, $complexity);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -179,11 +165,11 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
|
|
||||||
private function astFieldInfo(FieldNode $field)
|
private function astFieldInfo(FieldNode $field)
|
||||||
{
|
{
|
||||||
$fieldName = $this->getFieldName($field);
|
$fieldName = $this->getFieldName($field);
|
||||||
$astFieldInfo = [null, null];
|
$astFieldInfo = [null, null];
|
||||||
if (isset($this->fieldNodeAndDefs[$fieldName])) {
|
if (isset($this->fieldNodeAndDefs[$fieldName])) {
|
||||||
foreach ($this->fieldNodeAndDefs[$fieldName] as $astAndDef) {
|
foreach ($this->fieldNodeAndDefs[$fieldName] as $astAndDef) {
|
||||||
if ($astAndDef[0] == $field) {
|
if ($astAndDef[0] === $field) {
|
||||||
$astFieldInfo = $astAndDef;
|
$astFieldInfo = $astAndDef;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -193,37 +179,8 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
return $astFieldInfo;
|
return $astFieldInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildFieldArguments(FieldNode $node)
|
private function directiveExcludesField(FieldNode $node)
|
||||||
{
|
{
|
||||||
$rawVariableValues = $this->getRawVariableValues();
|
|
||||||
$astFieldInfo = $this->astFieldInfo($node);
|
|
||||||
$fieldDef = $astFieldInfo[1];
|
|
||||||
|
|
||||||
$args = [];
|
|
||||||
|
|
||||||
if ($fieldDef instanceof FieldDefinition) {
|
|
||||||
$variableValuesResult = Values::getVariableValues(
|
|
||||||
$this->context->getSchema(),
|
|
||||||
$this->variableDefs,
|
|
||||||
$rawVariableValues
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($variableValuesResult['errors']) {
|
|
||||||
throw new Error(implode("\n\n", array_map(
|
|
||||||
function ($error) {
|
|
||||||
return $error->getMessage();
|
|
||||||
}
|
|
||||||
, $variableValuesResult['errors'])));
|
|
||||||
}
|
|
||||||
$variableValues = $variableValuesResult['coerced'];
|
|
||||||
|
|
||||||
$args = Values::getArgumentValues($fieldDef, $node, $variableValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $args;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function directiveExcludesField(FieldNode $node) {
|
|
||||||
foreach ($node->directives as $directiveNode) {
|
foreach ($node->directives as $directiveNode) {
|
||||||
if ($directiveNode->name->value === 'deprecated') {
|
if ($directiveNode->name->value === 'deprecated') {
|
||||||
return false;
|
return false;
|
||||||
@ -236,28 +193,99 @@ class QueryComplexity extends AbstractQuerySecurity
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($variableValuesResult['errors']) {
|
if ($variableValuesResult['errors']) {
|
||||||
throw new Error(implode("\n\n", array_map(
|
throw new Error(implode(
|
||||||
function ($error) {
|
"\n\n",
|
||||||
return $error->getMessage();
|
array_map(
|
||||||
}
|
function ($error) {
|
||||||
, $variableValuesResult['errors'])));
|
return $error->getMessage();
|
||||||
|
},
|
||||||
|
$variableValuesResult['errors']
|
||||||
|
)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
$variableValues = $variableValuesResult['coerced'];
|
$variableValues = $variableValuesResult['coerced'];
|
||||||
|
|
||||||
if ($directiveNode->name->value === 'include') {
|
if ($directiveNode->name->value === 'include') {
|
||||||
$directive = Directive::includeDirective();
|
$directive = Directive::includeDirective();
|
||||||
$directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
$directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
||||||
|
|
||||||
return !$directiveArgs['if'];
|
return ! $directiveArgs['if'];
|
||||||
} else {
|
|
||||||
$directive = Directive::skipDirective();
|
|
||||||
$directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
|
||||||
|
|
||||||
return $directiveArgs['if'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$directive = Directive::skipDirective();
|
||||||
|
$directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
||||||
|
|
||||||
|
return $directiveArgs['if'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRawVariableValues()
|
||||||
|
{
|
||||||
|
return $this->rawVariableValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[]|null $rawVariableValues
|
||||||
|
*/
|
||||||
|
public function setRawVariableValues(?array $rawVariableValues = null)
|
||||||
|
{
|
||||||
|
$this->rawVariableValues = $rawVariableValues ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFieldArguments(FieldNode $node)
|
||||||
|
{
|
||||||
|
$rawVariableValues = $this->getRawVariableValues();
|
||||||
|
$astFieldInfo = $this->astFieldInfo($node);
|
||||||
|
$fieldDef = $astFieldInfo[1];
|
||||||
|
|
||||||
|
$args = [];
|
||||||
|
|
||||||
|
if ($fieldDef instanceof FieldDefinition) {
|
||||||
|
$variableValuesResult = Values::getVariableValues(
|
||||||
|
$this->context->getSchema(),
|
||||||
|
$this->variableDefs,
|
||||||
|
$rawVariableValues
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($variableValuesResult['errors']) {
|
||||||
|
throw new Error(implode(
|
||||||
|
"\n\n",
|
||||||
|
array_map(
|
||||||
|
function ($error) {
|
||||||
|
return $error->getMessage();
|
||||||
|
},
|
||||||
|
$variableValuesResult['errors']
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$variableValues = $variableValuesResult['coerced'];
|
||||||
|
|
||||||
|
$args = Values::getArgumentValues($fieldDef, $node, $variableValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaxQueryComplexity()
|
||||||
|
{
|
||||||
|
return $this->maxQueryComplexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set max query complexity. If equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
|
*/
|
||||||
|
public function setMaxQueryComplexity($maxQueryComplexity)
|
||||||
|
{
|
||||||
|
$this->checkIfGreaterOrEqualToZero('maxQueryComplexity', $maxQueryComplexity);
|
||||||
|
|
||||||
|
$this->maxQueryComplexity = (int) $maxQueryComplexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maxQueryComplexityErrorMessage($max, $count)
|
||||||
|
{
|
||||||
|
return sprintf('Max query complexity should be %d but got %d.', $max, $count);
|
||||||
|
}
|
||||||
|
|
||||||
protected function isEnabled()
|
protected function isEnabled()
|
||||||
{
|
{
|
||||||
return $this->getMaxQueryComplexity() !== static::DISABLED;
|
return $this->getMaxQueryComplexity() !== static::DISABLED;
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
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\Node;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\AST\SelectionSetNode;
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class QueryDepth extends AbstractQuerySecurity
|
class QueryDepth extends QuerySecurityRule
|
||||||
{
|
{
|
||||||
/**
|
/** @var int */
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $maxQueryDepth;
|
private $maxQueryDepth;
|
||||||
|
|
||||||
public function __construct($maxQueryDepth)
|
public function __construct($maxQueryDepth)
|
||||||
@ -23,28 +22,6 @@ class QueryDepth extends AbstractQuerySecurity
|
|||||||
$this->setMaxQueryDepth($maxQueryDepth);
|
$this->setMaxQueryDepth($maxQueryDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
|
|
||||||
*
|
|
||||||
* @param $maxQueryDepth
|
|
||||||
*/
|
|
||||||
public function setMaxQueryDepth($maxQueryDepth)
|
|
||||||
{
|
|
||||||
$this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
|
|
||||||
|
|
||||||
$this->maxQueryDepth = (int) $maxQueryDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMaxQueryDepth()
|
|
||||||
{
|
|
||||||
return $this->maxQueryDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function maxQueryDepthErrorMessage($max, $count)
|
|
||||||
{
|
|
||||||
return sprintf('Max query depth should be %d but got %d.', $max, $count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return $this->invokeIfNeeded(
|
return $this->invokeIfNeeded(
|
||||||
@ -54,22 +31,19 @@ class QueryDepth extends AbstractQuerySecurity
|
|||||||
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) {
|
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) {
|
||||||
$maxDepth = $this->fieldDepth($operationDefinition);
|
$maxDepth = $this->fieldDepth($operationDefinition);
|
||||||
|
|
||||||
if ($maxDepth > $this->getMaxQueryDepth()) {
|
if ($maxDepth <= $this->getMaxQueryDepth()) {
|
||||||
$context->reportError(
|
return;
|
||||||
new Error($this->maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(
|
||||||
|
new Error($this->maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth))
|
||||||
|
);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isEnabled()
|
|
||||||
{
|
|
||||||
return $this->getMaxQueryDepth() !== static::DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fieldDepth($node, $depth = 0, $maxDepth = 0)
|
private function fieldDepth($node, $depth = 0, $maxDepth = 0)
|
||||||
{
|
{
|
||||||
if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSetNode) {
|
if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSetNode) {
|
||||||
@ -85,9 +59,9 @@ class QueryDepth extends AbstractQuerySecurity
|
|||||||
{
|
{
|
||||||
switch ($node->kind) {
|
switch ($node->kind) {
|
||||||
case NodeKind::FIELD:
|
case NodeKind::FIELD:
|
||||||
/* @var FieldNode $node */
|
/** @var FieldNode $node */
|
||||||
// node has children?
|
// node has children?
|
||||||
if (null !== $node->selectionSet) {
|
if ($node->selectionSet !== null) {
|
||||||
// update maxDepth if needed
|
// update maxDepth if needed
|
||||||
if ($depth > $maxDepth) {
|
if ($depth > $maxDepth) {
|
||||||
$maxDepth = $depth;
|
$maxDepth = $depth;
|
||||||
@ -97,18 +71,18 @@ class QueryDepth extends AbstractQuerySecurity
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeKind::INLINE_FRAGMENT:
|
case NodeKind::INLINE_FRAGMENT:
|
||||||
/* @var InlineFragmentNode $node */
|
/** @var InlineFragmentNode $node */
|
||||||
// node has children?
|
// node has children?
|
||||||
if (null !== $node->selectionSet) {
|
if ($node->selectionSet !== null) {
|
||||||
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
|
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeKind::FRAGMENT_SPREAD:
|
case NodeKind::FRAGMENT_SPREAD:
|
||||||
/* @var FragmentSpreadNode $node */
|
/** @var FragmentSpreadNode $node */
|
||||||
$fragment = $this->getFragment($node);
|
$fragment = $this->getFragment($node);
|
||||||
|
|
||||||
if (null !== $fragment) {
|
if ($fragment !== null) {
|
||||||
$maxDepth = $this->fieldDepth($fragment, $depth, $maxDepth);
|
$maxDepth = $this->fieldDepth($fragment, $depth, $maxDepth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -116,4 +90,31 @@ class QueryDepth extends AbstractQuerySecurity
|
|||||||
|
|
||||||
return $maxDepth;
|
return $maxDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMaxQueryDepth()
|
||||||
|
{
|
||||||
|
return $this->maxQueryDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
|
*
|
||||||
|
* @param int $maxQueryDepth
|
||||||
|
*/
|
||||||
|
public function setMaxQueryDepth($maxQueryDepth)
|
||||||
|
{
|
||||||
|
$this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
|
||||||
|
|
||||||
|
$this->maxQueryDepth = (int) $maxQueryDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maxQueryDepthErrorMessage($max, $count)
|
||||||
|
{
|
||||||
|
return sprintf('Max query depth should be %d but got %d.', $max, $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isEnabled()
|
||||||
|
{
|
||||||
|
return $this->getMaxQueryDepth() !== static::DISABLED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use GraphQL\Language\AST\FieldNode;
|
use GraphQL\Language\AST\FieldNode;
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\InlineFragmentNode;
|
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\SelectionSetNode;
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function class_alias;
|
||||||
|
use function method_exists;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
abstract class AbstractQuerySecurity extends AbstractValidationRule
|
abstract class QuerySecurityRule extends ValidationRule
|
||||||
{
|
{
|
||||||
const DISABLED = 0;
|
public const DISABLED = 0;
|
||||||
|
|
||||||
/**
|
/** @var FragmentDefinitionNode[] */
|
||||||
* @var FragmentDefinitionNode[]
|
|
||||||
*/
|
|
||||||
private $fragments = [];
|
private $fragments = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \GraphQL\Language\AST\FragmentDefinitionNode[]
|
|
||||||
*/
|
|
||||||
protected function getFragments()
|
|
||||||
{
|
|
||||||
return $this->fragments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if equal to 0 no check is done. Must be greater or equal to 0.
|
* check if equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
*
|
*
|
||||||
* @param $value
|
* @param string $name
|
||||||
|
* @param int $value
|
||||||
*/
|
*/
|
||||||
protected function checkIfGreaterOrEqualToZero($name, $value)
|
protected function checkIfGreaterOrEqualToZero($name, $value)
|
||||||
{
|
{
|
||||||
@ -43,30 +38,30 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function gatherFragmentDefinition(ValidationContext $context)
|
|
||||||
{
|
|
||||||
// Gather all the fragment definition.
|
|
||||||
// Importantly this does not include inline fragments.
|
|
||||||
$definitions = $context->getDocument()->definitions;
|
|
||||||
foreach ($definitions as $node) {
|
|
||||||
if ($node instanceof FragmentDefinitionNode) {
|
|
||||||
$this->fragments[$node->name->value] = $node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getFragment(FragmentSpreadNode $fragmentSpread)
|
protected function getFragment(FragmentSpreadNode $fragmentSpread)
|
||||||
{
|
{
|
||||||
$spreadName = $fragmentSpread->name->value;
|
$spreadName = $fragmentSpread->name->value;
|
||||||
$fragments = $this->getFragments();
|
$fragments = $this->getFragments();
|
||||||
|
|
||||||
return isset($fragments[$spreadName]) ? $fragments[$spreadName] : null;
|
return $fragments[$spreadName] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FragmentDefinitionNode[]
|
||||||
|
*/
|
||||||
|
protected function getFragments()
|
||||||
|
{
|
||||||
|
return $this->fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Closure[] $validators
|
||||||
|
* @return Closure[]
|
||||||
|
*/
|
||||||
protected function invokeIfNeeded(ValidationContext $context, array $validators)
|
protected function invokeIfNeeded(ValidationContext $context, array $validators)
|
||||||
{
|
{
|
||||||
// is disabled?
|
// is disabled?
|
||||||
if (!$this->isEnabled()) {
|
if (! $this->isEnabled()) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +70,22 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
|
|||||||
return $validators;
|
return $validators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract protected function isEnabled();
|
||||||
|
|
||||||
|
protected function gatherFragmentDefinition(ValidationContext $context)
|
||||||
|
{
|
||||||
|
// Gather all the fragment definition.
|
||||||
|
// Importantly this does not include inline fragments.
|
||||||
|
$definitions = $context->getDocument()->definitions;
|
||||||
|
foreach ($definitions as $node) {
|
||||||
|
if (! ($node instanceof FragmentDefinitionNode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fragments[$node->name->value] = $node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a selectionSet, adds all of the fields in that selection to
|
* Given a selectionSet, adds all of the fields in that selection to
|
||||||
* the passed in map of fields, and returns it at the end.
|
* the passed in map of fields, and returns it at the end.
|
||||||
@ -85,29 +96,30 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
|
|||||||
*
|
*
|
||||||
* @see \GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged
|
* @see \GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged
|
||||||
*
|
*
|
||||||
* @param ValidationContext $context
|
* @param Type|null $parentType
|
||||||
* @param Type|null $parentType
|
|
||||||
* @param SelectionSetNode $selectionSet
|
|
||||||
* @param \ArrayObject $visitedFragmentNames
|
|
||||||
* @param \ArrayObject $astAndDefs
|
|
||||||
*
|
*
|
||||||
* @return \ArrayObject
|
* @return \ArrayObject
|
||||||
*/
|
*/
|
||||||
protected function collectFieldASTsAndDefs(ValidationContext $context, $parentType, SelectionSetNode $selectionSet, \ArrayObject $visitedFragmentNames = null, \ArrayObject $astAndDefs = null)
|
protected function collectFieldASTsAndDefs(
|
||||||
{
|
ValidationContext $context,
|
||||||
|
$parentType,
|
||||||
|
SelectionSetNode $selectionSet,
|
||||||
|
?\ArrayObject $visitedFragmentNames = null,
|
||||||
|
?\ArrayObject $astAndDefs = null
|
||||||
|
) {
|
||||||
$_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject();
|
$_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject();
|
||||||
$_astAndDefs = $astAndDefs ?: new \ArrayObject();
|
$_astAndDefs = $astAndDefs ?: new \ArrayObject();
|
||||||
|
|
||||||
foreach ($selectionSet->selections as $selection) {
|
foreach ($selectionSet->selections as $selection) {
|
||||||
switch ($selection->kind) {
|
switch ($selection->getKind()) {
|
||||||
case NodeKind::FIELD:
|
case NodeKind::FIELD:
|
||||||
/* @var FieldNode $selection */
|
/** @var FieldNode $selection */
|
||||||
$fieldName = $selection->name->value;
|
$fieldName = $selection->name->value;
|
||||||
$fieldDef = null;
|
$fieldDef = null;
|
||||||
if ($parentType && method_exists($parentType, 'getFields')) {
|
if ($parentType && method_exists($parentType, 'getFields')) {
|
||||||
$tmp = $parentType->getFields();
|
$tmp = $parentType->getFields();
|
||||||
$schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
|
$schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
|
||||||
$typeMetaFieldDef = Introspection::typeMetaFieldDef();
|
$typeMetaFieldDef = Introspection::typeMetaFieldDef();
|
||||||
$typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
|
$typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
|
||||||
|
|
||||||
if ($fieldName === $schemaMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
|
if ($fieldName === $schemaMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
|
||||||
@ -121,14 +133,14 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$responseName = $this->getFieldName($selection);
|
$responseName = $this->getFieldName($selection);
|
||||||
if (!isset($_astAndDefs[$responseName])) {
|
if (! isset($_astAndDefs[$responseName])) {
|
||||||
$_astAndDefs[$responseName] = new \ArrayObject();
|
$_astAndDefs[$responseName] = new \ArrayObject();
|
||||||
}
|
}
|
||||||
// create field context
|
// create field context
|
||||||
$_astAndDefs[$responseName][] = [$selection, $fieldDef];
|
$_astAndDefs[$responseName][] = [$selection, $fieldDef];
|
||||||
break;
|
break;
|
||||||
case NodeKind::INLINE_FRAGMENT:
|
case NodeKind::INLINE_FRAGMENT:
|
||||||
/* @var InlineFragmentNode $selection */
|
/** @var InlineFragmentNode $selection */
|
||||||
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
||||||
$context,
|
$context,
|
||||||
TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
|
TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
|
||||||
@ -138,12 +150,12 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case NodeKind::FRAGMENT_SPREAD:
|
case NodeKind::FRAGMENT_SPREAD:
|
||||||
/* @var FragmentSpreadNode $selection */
|
/** @var FragmentSpreadNode $selection */
|
||||||
$fragName = $selection->name->value;
|
$fragName = $selection->name->value;
|
||||||
|
|
||||||
if (empty($_visitedFragmentNames[$fragName])) {
|
if (empty($_visitedFragmentNames[$fragName])) {
|
||||||
$_visitedFragmentNames[$fragName] = true;
|
$_visitedFragmentNames[$fragName] = true;
|
||||||
$fragment = $context->getFragment($fragName);
|
$fragment = $context->getFragment($fragName);
|
||||||
|
|
||||||
if ($fragment) {
|
if ($fragment) {
|
||||||
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
$_astAndDefs = $this->collectFieldASTsAndDefs(
|
||||||
@ -165,10 +177,9 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
|
|||||||
protected function getFieldName(FieldNode $node)
|
protected function getFieldName(FieldNode $node)
|
||||||
{
|
{
|
||||||
$fieldName = $node->name->value;
|
$fieldName = $node->name->value;
|
||||||
$responseName = $node->alias ? $node->alias->value : $fieldName;
|
|
||||||
|
|
||||||
return $responseName;
|
return $node->alias ? $node->alias->value : $fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected function isEnabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class_alias(QuerySecurityRule::class, 'GraphQL\Validator\Rules\AbstractQuerySecurity');
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -6,40 +9,43 @@ use GraphQL\Language\AST\FieldNode;
|
|||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class ScalarLeafs extends AbstractValidationRule
|
class ScalarLeafs extends ValidationRule
|
||||||
{
|
{
|
||||||
static function noSubselectionAllowedMessage($field, $type)
|
|
||||||
{
|
|
||||||
return "Field \"$field\" of type \"$type\" must not have a sub selection.";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function requiredSubselectionMessage($field, $type)
|
|
||||||
{
|
|
||||||
return "Field \"$field\" of type \"$type\" must have a sub selection.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::FIELD => function(FieldNode $node) use ($context) {
|
NodeKind::FIELD => function (FieldNode $node) use ($context) {
|
||||||
$type = $context->getType();
|
$type = $context->getType();
|
||||||
if ($type) {
|
if (! $type) {
|
||||||
if (Type::isLeafType(Type::getNamedType($type))) {
|
return;
|
||||||
if ($node->selectionSet) {
|
}
|
||||||
$context->reportError(new Error(
|
|
||||||
self::noSubselectionAllowedMessage($node->name->value, $type),
|
if (Type::isLeafType(Type::getNamedType($type))) {
|
||||||
[$node->selectionSet]
|
if ($node->selectionSet) {
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if (!$node->selectionSet) {
|
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::requiredSubselectionMessage($node->name->value, $type),
|
self::noSubselectionAllowedMessage($node->name->value, $type),
|
||||||
[$node]
|
[$node->selectionSet]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
} elseif (! $node->selectionSet) {
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::requiredSubselectionMessage($node->name->value, $type),
|
||||||
|
[$node]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function noSubselectionAllowedMessage($field, $type)
|
||||||
|
{
|
||||||
|
return sprintf('Field "%s" of type "%s" must not have a sub selection.', $field, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function requiredSubselectionMessage($field, $type)
|
||||||
|
{
|
||||||
|
return sprintf('Field "%s" of type "%s" must have a sub selection.', $field, $type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\ArgumentNode;
|
use GraphQL\Language\AST\ArgumentNode;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class UniqueArgumentNames extends AbstractValidationRule
|
class UniqueArgumentNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function duplicateArgMessage($argName)
|
/** @var NameNode[] */
|
||||||
{
|
|
||||||
return "There can be only one argument named \"$argName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $knownArgNames;
|
public $knownArgNames;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -22,15 +22,15 @@ class UniqueArgumentNames extends AbstractValidationRule
|
|||||||
$this->knownArgNames = [];
|
$this->knownArgNames = [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::FIELD => function () {
|
NodeKind::FIELD => function () {
|
||||||
$this->knownArgNames = [];;
|
$this->knownArgNames = [];
|
||||||
},
|
},
|
||||||
NodeKind::DIRECTIVE => function () {
|
NodeKind::DIRECTIVE => function () {
|
||||||
$this->knownArgNames = [];
|
$this->knownArgNames = [];
|
||||||
},
|
},
|
||||||
NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) {
|
NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) {
|
||||||
$argName = $node->name->value;
|
$argName = $node->name->value;
|
||||||
if (!empty($this->knownArgNames[$argName])) {
|
if (! empty($this->knownArgNames[$argName])) {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::duplicateArgMessage($argName),
|
self::duplicateArgMessage($argName),
|
||||||
[$this->knownArgNames[$argName], $node->name]
|
[$this->knownArgNames[$argName], $node->name]
|
||||||
@ -38,8 +38,14 @@ class UniqueArgumentNames extends AbstractValidationRule
|
|||||||
} else {
|
} else {
|
||||||
$this->knownArgNames[$argName] = $node->name;
|
$this->knownArgNames[$argName] = $node->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function duplicateArgMessage($argName)
|
||||||
|
{
|
||||||
|
return sprintf('There can be only one argument named "%s".', $argName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,44 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\DirectiveNode;
|
use GraphQL\Language\AST\DirectiveNode;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class UniqueDirectivesPerLocation extends AbstractValidationRule
|
class UniqueDirectivesPerLocation extends ValidationRule
|
||||||
{
|
{
|
||||||
static function duplicateDirectiveMessage($directiveName)
|
|
||||||
{
|
|
||||||
return 'The directive "'.$directiveName.'" can only be used once at this location.';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'enter' => function(Node $node) use ($context) {
|
'enter' => function (Node $node) use ($context) {
|
||||||
if (isset($node->directives)) {
|
if (! isset($node->directives)) {
|
||||||
$knownDirectives = [];
|
return;
|
||||||
foreach ($node->directives as $directive) {
|
}
|
||||||
/** @var DirectiveNode $directive */
|
|
||||||
$directiveName = $directive->name->value;
|
$knownDirectives = [];
|
||||||
if (isset($knownDirectives[$directiveName])) {
|
foreach ($node->directives as $directive) {
|
||||||
$context->reportError(new Error(
|
/** @var DirectiveNode $directive */
|
||||||
self::duplicateDirectiveMessage($directiveName),
|
$directiveName = $directive->name->value;
|
||||||
[$knownDirectives[$directiveName], $directive]
|
if (isset($knownDirectives[$directiveName])) {
|
||||||
));
|
$context->reportError(new Error(
|
||||||
} else {
|
self::duplicateDirectiveMessage($directiveName),
|
||||||
$knownDirectives[$directiveName] = $directive;
|
[$knownDirectives[$directiveName], $directive]
|
||||||
}
|
));
|
||||||
|
} else {
|
||||||
|
$knownDirectives[$directiveName] = $directive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function duplicateDirectiveMessage($directiveName)
|
||||||
|
{
|
||||||
|
return sprintf('The directive "%s" can only be used once at this location.', $directiveName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class UniqueFragmentNames extends AbstractValidationRule
|
class UniqueFragmentNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function duplicateFragmentNameMessage($fragName)
|
/** @var NameNode[] */
|
||||||
{
|
|
||||||
return "There can be only one fragment named \"$fragName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $knownFragmentNames;
|
public $knownFragmentNames;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -24,18 +25,24 @@ class UniqueFragmentNames extends AbstractValidationRule
|
|||||||
NodeKind::OPERATION_DEFINITION => function () {
|
NodeKind::OPERATION_DEFINITION => function () {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
||||||
$fragmentName = $node->name->value;
|
$fragmentName = $node->name->value;
|
||||||
if (!empty($this->knownFragmentNames[$fragmentName])) {
|
if (empty($this->knownFragmentNames[$fragmentName])) {
|
||||||
|
$this->knownFragmentNames[$fragmentName] = $node->name;
|
||||||
|
} else {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::duplicateFragmentNameMessage($fragmentName),
|
self::duplicateFragmentNameMessage($fragmentName),
|
||||||
[ $this->knownFragmentNames[$fragmentName], $node->name ]
|
[$this->knownFragmentNames[$fragmentName], $node->name]
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
$this->knownFragmentNames[$fragmentName] = $node->name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function duplicateFragmentNameMessage($fragName)
|
||||||
|
{
|
||||||
|
return sprintf('There can be only one fragment named "%s".', $fragName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -6,45 +9,51 @@ use GraphQL\Language\AST\NodeKind;
|
|||||||
use GraphQL\Language\AST\ObjectFieldNode;
|
use GraphQL\Language\AST\ObjectFieldNode;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function array_pop;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class UniqueInputFieldNames extends AbstractValidationRule
|
class UniqueInputFieldNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function duplicateInputFieldMessage($fieldName)
|
/** @var string[] */
|
||||||
{
|
|
||||||
return "There can be only one input field named \"$fieldName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $knownNames;
|
public $knownNames;
|
||||||
|
|
||||||
|
/** @var string[][] */
|
||||||
public $knownNameStack;
|
public $knownNameStack;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$this->knownNames = [];
|
$this->knownNames = [];
|
||||||
$this->knownNameStack = [];
|
$this->knownNameStack = [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::OBJECT => [
|
NodeKind::OBJECT => [
|
||||||
'enter' => function() {
|
'enter' => function () {
|
||||||
$this->knownNameStack[] = $this->knownNames;
|
$this->knownNameStack[] = $this->knownNames;
|
||||||
$this->knownNames = [];
|
$this->knownNames = [];
|
||||||
},
|
},
|
||||||
'leave' => function() {
|
'leave' => function () {
|
||||||
$this->knownNames = array_pop($this->knownNameStack);
|
$this->knownNames = array_pop($this->knownNameStack);
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) use ($context) {
|
NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) {
|
||||||
$fieldName = $node->name->value;
|
$fieldName = $node->name->value;
|
||||||
|
|
||||||
if (!empty($this->knownNames[$fieldName])) {
|
if (! empty($this->knownNames[$fieldName])) {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::duplicateInputFieldMessage($fieldName),
|
self::duplicateInputFieldMessage($fieldName),
|
||||||
[ $this->knownNames[$fieldName], $node->name ]
|
[$this->knownNames[$fieldName], $node->name]
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
$this->knownNames[$fieldName] = $node->name;
|
$this->knownNames[$fieldName] = $node->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function duplicateInputFieldMessage($fieldName)
|
||||||
|
{
|
||||||
|
return sprintf('There can be only one input field named "%s".', $fieldName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class UniqueOperationNames extends AbstractValidationRule
|
class UniqueOperationNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function duplicateOperationNameMessage($operationName)
|
/** @var NameNode[] */
|
||||||
{
|
|
||||||
return "There can be only one operation named \"$operationName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $knownOperationNames;
|
public $knownOperationNames;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -21,24 +22,30 @@ class UniqueOperationNames extends AbstractValidationRule
|
|||||||
$this->knownOperationNames = [];
|
$this->knownOperationNames = [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) use ($context) {
|
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) {
|
||||||
$operationName = $node->name;
|
$operationName = $node->name;
|
||||||
|
|
||||||
if ($operationName) {
|
if ($operationName) {
|
||||||
if (!empty($this->knownOperationNames[$operationName->value])) {
|
if (empty($this->knownOperationNames[$operationName->value])) {
|
||||||
|
$this->knownOperationNames[$operationName->value] = $operationName;
|
||||||
|
} else {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::duplicateOperationNameMessage($operationName->value),
|
self::duplicateOperationNameMessage($operationName->value),
|
||||||
[ $this->knownOperationNames[$operationName->value], $operationName ]
|
[$this->knownOperationNames[$operationName->value], $operationName]
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
$this->knownOperationNames[$operationName->value] = $operationName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function() {
|
NodeKind::FRAGMENT_DEFINITION => function () {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function duplicateOperationNameMessage($operationName)
|
||||||
|
{
|
||||||
|
return sprintf('There can be only one operation named "%s".', $operationName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class UniqueVariableNames extends AbstractValidationRule
|
class UniqueVariableNames extends ValidationRule
|
||||||
{
|
{
|
||||||
static function duplicateVariableMessage($variableName)
|
/** @var NameNode[] */
|
||||||
{
|
|
||||||
return "There can be only one variable named \"$variableName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $knownVariableNames;
|
public $knownVariableNames;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -20,20 +21,25 @@ class UniqueVariableNames extends AbstractValidationRule
|
|||||||
$this->knownVariableNames = [];
|
$this->knownVariableNames = [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::OPERATION_DEFINITION => function() {
|
NodeKind::OPERATION_DEFINITION => function () {
|
||||||
$this->knownVariableNames = [];
|
$this->knownVariableNames = [];
|
||||||
},
|
},
|
||||||
NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) use ($context) {
|
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
|
||||||
$variableName = $node->variable->name->value;
|
$variableName = $node->variable->name->value;
|
||||||
if (!empty($this->knownVariableNames[$variableName])) {
|
if (empty($this->knownVariableNames[$variableName])) {
|
||||||
|
$this->knownVariableNames[$variableName] = $node->variable->name;
|
||||||
|
} else {
|
||||||
$context->reportError(new Error(
|
$context->reportError(new Error(
|
||||||
self::duplicateVariableMessage($variableName),
|
self::duplicateVariableMessage($variableName),
|
||||||
[ $this->knownVariableNames[$variableName], $node->variable->name ]
|
[$this->knownVariableNames[$variableName], $node->variable->name]
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
$this->knownVariableNames[$variableName] = $node->variable->name;
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function duplicateVariableMessage($variableName)
|
||||||
|
{
|
||||||
|
return sprintf('There can be only one variable named "%s".', $variableName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function class_alias;
|
||||||
|
use function get_class;
|
||||||
|
|
||||||
abstract class AbstractValidationRule
|
abstract class ValidationRule
|
||||||
{
|
{
|
||||||
|
/** @var string */
|
||||||
protected $name;
|
protected $name;
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
@ -21,8 +27,9 @@ abstract class AbstractValidationRule
|
|||||||
* Returns structure suitable for GraphQL\Language\Visitor
|
* Returns structure suitable for GraphQL\Language\Visitor
|
||||||
*
|
*
|
||||||
* @see \GraphQL\Language\Visitor
|
* @see \GraphQL\Language\Visitor
|
||||||
* @param ValidationContext $context
|
* @return mixed[]
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
abstract public function getVisitor(ValidationContext $context);
|
abstract public function getVisitor(ValidationContext $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class_alias(ValidationRule::class, 'GraphQL\Validator\Rules\AbstractValidationRule');
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -24,6 +27,12 @@ use GraphQL\Type\Definition\ScalarType;
|
|||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function array_combine;
|
||||||
|
use function array_keys;
|
||||||
|
use function array_map;
|
||||||
|
use function array_values;
|
||||||
|
use function iterator_to_array;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value literals of correct type
|
* Value literals of correct type
|
||||||
@ -31,103 +40,96 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
* A GraphQL document is only valid if all value literals are of the type
|
* A GraphQL document is only valid if all value literals are of the type
|
||||||
* expected at their position.
|
* expected at their position.
|
||||||
*/
|
*/
|
||||||
class ValuesOfCorrectType extends AbstractValidationRule
|
class ValuesOfCorrectType extends ValidationRule
|
||||||
{
|
{
|
||||||
static function badValueMessage($typeName, $valueName, $message = null)
|
|
||||||
{
|
|
||||||
return "Expected type {$typeName}, found {$valueName}" .
|
|
||||||
($message ? "; ${message}" : '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName)
|
|
||||||
{
|
|
||||||
return "Field {$typeName}.{$fieldName} of required type " .
|
|
||||||
"{$fieldTypeName} was not provided.";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function unknownFieldMessage($typeName, $fieldName, $message = null)
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
"Field \"{$fieldName}\" is not defined by type {$typeName}" .
|
|
||||||
($message ? "; {$message}" : '.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::NULL => function(NullValueNode $node) use ($context) {
|
NodeKind::NULL => function (NullValueNode $node) use ($context) {
|
||||||
$type = $context->getInputType();
|
$type = $context->getInputType();
|
||||||
if ($type instanceof NonNull) {
|
if (! ($type instanceof NonNull)) {
|
||||||
$context->reportError(
|
return;
|
||||||
new Error(
|
|
||||||
self::badValueMessage((string) $type, Printer::doPrint($node)),
|
|
||||||
$node
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage((string) $type, Printer::doPrint($node)),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
NodeKind::LST => function(ListValueNode $node) use ($context) {
|
NodeKind::LST => function (ListValueNode $node) use ($context) {
|
||||||
// Note: TypeInfo will traverse into a list's item type, so look to the
|
// Note: TypeInfo will traverse into a list's item type, so look to the
|
||||||
// parent input type to check if it is a list.
|
// parent input type to check if it is a list.
|
||||||
$type = Type::getNullableType($context->getParentInputType());
|
$type = Type::getNullableType($context->getParentInputType());
|
||||||
if (!$type instanceof ListOfType) {
|
if (! $type instanceof ListOfType) {
|
||||||
$this->isValidScalar($context, $node);
|
$this->isValidScalar($context, $node);
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NodeKind::OBJECT => function(ObjectValueNode $node) use ($context) {
|
NodeKind::OBJECT => function (ObjectValueNode $node) use ($context) {
|
||||||
// Note: TypeInfo will traverse into a list's item type, so look to the
|
// Note: TypeInfo will traverse into a list's item type, so look to the
|
||||||
// parent input type to check if it is a list.
|
// parent input type to check if it is a list.
|
||||||
$type = Type::getNamedType($context->getInputType());
|
$type = Type::getNamedType($context->getInputType());
|
||||||
if (!$type instanceof InputObjectType) {
|
if (! $type instanceof InputObjectType) {
|
||||||
$this->isValidScalar($context, $node);
|
$this->isValidScalar($context, $node);
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
}
|
}
|
||||||
// Ensure every required field exists.
|
// Ensure every required field exists.
|
||||||
$inputFields = $type->getFields();
|
$inputFields = $type->getFields();
|
||||||
$nodeFields = iterator_to_array($node->fields);
|
$nodeFields = iterator_to_array($node->fields);
|
||||||
$fieldNodeMap = array_combine(
|
$fieldNodeMap = array_combine(
|
||||||
array_map(function ($field) { return $field->name->value; }, $nodeFields),
|
array_map(
|
||||||
|
function ($field) {
|
||||||
|
return $field->name->value;
|
||||||
|
},
|
||||||
|
$nodeFields
|
||||||
|
),
|
||||||
array_values($nodeFields)
|
array_values($nodeFields)
|
||||||
);
|
);
|
||||||
foreach ($inputFields as $fieldName => $fieldDef) {
|
foreach ($inputFields as $fieldName => $fieldDef) {
|
||||||
$fieldType = $fieldDef->getType();
|
$fieldType = $fieldDef->getType();
|
||||||
if (!isset($fieldNodeMap[$fieldName]) && $fieldType instanceof NonNull) {
|
if (isset($fieldNodeMap[$fieldName]) || ! ($fieldType instanceof NonNull)) {
|
||||||
$context->reportError(
|
continue;
|
||||||
new Error(
|
|
||||||
self::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
|
|
||||||
$node
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) use ($context) {
|
|
||||||
$parentType = Type::getNamedType($context->getParentInputType());
|
|
||||||
$fieldType = $context->getInputType();
|
|
||||||
if (!$fieldType && $parentType instanceof InputObjectType) {
|
|
||||||
$suggestions = Utils::suggestionList(
|
|
||||||
$node->name->value,
|
|
||||||
array_keys($parentType->getFields())
|
|
||||||
);
|
|
||||||
$didYouMean = $suggestions
|
|
||||||
? "Did you mean " . Utils::orList($suggestions) . "?"
|
|
||||||
: null;
|
|
||||||
|
|
||||||
$context->reportError(
|
$context->reportError(
|
||||||
new Error(
|
new Error(
|
||||||
self::unknownFieldMessage($parentType->name, $node->name->value, $didYouMean),
|
self::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
|
||||||
$node
|
$node
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NodeKind::ENUM => function(EnumValueNode $node) use ($context) {
|
NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) {
|
||||||
|
$parentType = Type::getNamedType($context->getParentInputType());
|
||||||
|
$fieldType = $context->getInputType();
|
||||||
|
if ($fieldType || ! ($parentType instanceof InputObjectType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$suggestions = Utils::suggestionList(
|
||||||
|
$node->name->value,
|
||||||
|
array_keys($parentType->getFields())
|
||||||
|
);
|
||||||
|
$didYouMean = $suggestions
|
||||||
|
? 'Did you mean ' . Utils::orList($suggestions) . '?'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::unknownFieldMessage($parentType->name, $node->name->value, $didYouMean),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
NodeKind::ENUM => function (EnumValueNode $node) use ($context) {
|
||||||
$type = Type::getNamedType($context->getInputType());
|
$type = Type::getNamedType($context->getInputType());
|
||||||
if (!$type instanceof EnumType) {
|
if (! $type instanceof EnumType) {
|
||||||
$this->isValidScalar($context, $node);
|
$this->isValidScalar($context, $node);
|
||||||
} else if (!$type->getValue($node->value)) {
|
} elseif (! $type->getValue($node->value)) {
|
||||||
$context->reportError(
|
$context->reportError(
|
||||||
new Error(
|
new Error(
|
||||||
self::badValueMessage(
|
self::badValueMessage(
|
||||||
@ -140,25 +142,39 @@ class ValuesOfCorrectType extends AbstractValidationRule
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NodeKind::INT => function (IntValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
NodeKind::INT => function (IntValueNode $node) use ($context) {
|
||||||
NodeKind::FLOAT => function (FloatValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
$this->isValidScalar($context, $node);
|
||||||
NodeKind::STRING => function (StringValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
},
|
||||||
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
NodeKind::FLOAT => function (FloatValueNode $node) use ($context) {
|
||||||
|
$this->isValidScalar($context, $node);
|
||||||
|
},
|
||||||
|
NodeKind::STRING => function (StringValueNode $node) use ($context) {
|
||||||
|
$this->isValidScalar($context, $node);
|
||||||
|
},
|
||||||
|
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) {
|
||||||
|
$this->isValidScalar($context, $node);
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function badValueMessage($typeName, $valueName, $message = null)
|
||||||
|
{
|
||||||
|
return sprintf('Expected type %s, found %s', $typeName, $valueName) .
|
||||||
|
($message ? "; ${message}" : '.');
|
||||||
|
}
|
||||||
|
|
||||||
private function isValidScalar(ValidationContext $context, ValueNode $node)
|
private function isValidScalar(ValidationContext $context, ValueNode $node)
|
||||||
{
|
{
|
||||||
// Report any error at the full type expected by the location.
|
// Report any error at the full type expected by the location.
|
||||||
$locationType = $context->getInputType();
|
$locationType = $context->getInputType();
|
||||||
|
|
||||||
if (!$locationType) {
|
if (! $locationType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = Type::getNamedType($locationType);
|
$type = Type::getNamedType($locationType);
|
||||||
|
|
||||||
if (!$type instanceof ScalarType) {
|
if (! $type instanceof ScalarType) {
|
||||||
$context->reportError(
|
$context->reportError(
|
||||||
new Error(
|
new Error(
|
||||||
self::badValueMessage(
|
self::badValueMessage(
|
||||||
@ -169,6 +185,7 @@ class ValuesOfCorrectType extends AbstractValidationRule
|
|||||||
$node
|
$node
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,12 +233,26 @@ class ValuesOfCorrectType extends AbstractValidationRule
|
|||||||
if ($type instanceof EnumType) {
|
if ($type instanceof EnumType) {
|
||||||
$suggestions = Utils::suggestionList(
|
$suggestions = Utils::suggestionList(
|
||||||
Printer::doPrint($node),
|
Printer::doPrint($node),
|
||||||
array_map(function (EnumValueDefinition $value) {
|
array_map(
|
||||||
return $value->name;
|
function (EnumValueDefinition $value) {
|
||||||
}, $type->getValues())
|
return $value->name;
|
||||||
|
},
|
||||||
|
$type->getValues()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return $suggestions ? 'Did you mean the enum value ' . Utils::orList($suggestions) . '?' : null;
|
return $suggestions ? 'Did you mean the enum value ' . Utils::orList($suggestions) . '?' : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName)
|
||||||
|
{
|
||||||
|
return sprintf('Field %s.%s of required type %s was not provided.', $typeName, $fieldName, $fieldTypeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unknownFieldMessage($typeName, $fieldName, $message = null)
|
||||||
|
{
|
||||||
|
return sprintf('Field "%s" is not defined by type %s', $fieldName, $typeName) .
|
||||||
|
($message ? sprintf('; %s', $message) : '.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,42 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Language\Printer;
|
use GraphQL\Language\Printer;
|
||||||
use GraphQL\Type\Definition\InputType;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class VariablesAreInputTypes extends AbstractValidationRule
|
class VariablesAreInputTypes extends ValidationRule
|
||||||
{
|
{
|
||||||
static function nonInputTypeOnVarMessage($variableName, $typeName)
|
|
||||||
{
|
|
||||||
return "Variable \"\$$variableName\" cannot be non-input type \"$typeName\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) use ($context) {
|
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
|
||||||
$type = TypeInfo::typeFromAST($context->getSchema(), $node->type);
|
$type = TypeInfo::typeFromAST($context->getSchema(), $node->type);
|
||||||
|
|
||||||
// If the variable type is not an input type, return an error.
|
// If the variable type is not an input type, return an error.
|
||||||
if ($type && !Type::isInputType($type)) {
|
if (! $type || Type::isInputType($type)) {
|
||||||
$variableName = $node->variable->name->value;
|
return;
|
||||||
$context->reportError(new Error(
|
|
||||||
self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
|
|
||||||
[ $node->type ]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
$variableName = $node->variable->name->value;
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
|
||||||
|
[$node->type]
|
||||||
|
));
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function nonInputTypeOnVarMessage($variableName, $typeName)
|
||||||
|
{
|
||||||
|
return sprintf('Variable "$%s" cannot be non-input type "%s".', $variableName, $typeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -9,6 +12,7 @@ use GraphQL\Language\AST\VariableDefinitionNode;
|
|||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Type\Definition\NonNull;
|
use GraphQL\Type\Definition\NonNull;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable's default value is allowed
|
* Variable's default value is allowed
|
||||||
@ -16,45 +20,46 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
* A GraphQL document is only valid if all variable default values are allowed
|
* A GraphQL document is only valid if all variable default values are allowed
|
||||||
* due to a variable not being required.
|
* due to a variable not being required.
|
||||||
*/
|
*/
|
||||||
class VariablesDefaultValueAllowed extends AbstractValidationRule
|
class VariablesDefaultValueAllowed extends ValidationRule
|
||||||
{
|
{
|
||||||
static function defaultForRequiredVarMessage($varName, $type, $guessType)
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
"Variable \"\${$varName}\" of type \"{$type}\" is required and " .
|
|
||||||
'will not use the default value. ' .
|
|
||||||
"Perhaps you meant to use type \"{$guessType}\"."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) use ($context) {
|
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
|
||||||
$name = $node->variable->name->value;
|
$name = $node->variable->name->value;
|
||||||
$defaultValue = $node->defaultValue;
|
$defaultValue = $node->defaultValue;
|
||||||
$type = $context->getInputType();
|
$type = $context->getInputType();
|
||||||
if ($type instanceof NonNull && $defaultValue) {
|
if ($type instanceof NonNull && $defaultValue) {
|
||||||
$context->reportError(
|
$context->reportError(
|
||||||
new Error(
|
new Error(
|
||||||
self::defaultForRequiredVarMessage(
|
self::defaultForRequiredVarMessage(
|
||||||
$name,
|
$name,
|
||||||
$type,
|
$type,
|
||||||
$type->getWrappedType()
|
$type->getWrappedType()
|
||||||
),
|
),
|
||||||
[$defaultValue]
|
[$defaultValue]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::SELECTION_SET => function(SelectionSetNode $node) use ($context) {
|
NodeKind::SELECTION_SET => function (SelectionSetNode $node) use ($context) {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) use ($context) {
|
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
|
||||||
return Visitor::skipNode();
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator\Rules;
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
@ -10,15 +13,11 @@ use GraphQL\Type\Definition\NonNull;
|
|||||||
use GraphQL\Utils\TypeComparators;
|
use GraphQL\Utils\TypeComparators;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Validator\ValidationContext;
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
class VariablesInAllowedPosition extends AbstractValidationRule
|
class VariablesInAllowedPosition extends ValidationRule
|
||||||
{
|
{
|
||||||
static function badVarPosMessage($varName, $varType, $expectedType)
|
/** @var */
|
||||||
{
|
|
||||||
return "Variable \"\$$varName\" of type \"$varType\" used in position expecting ".
|
|
||||||
"type \"$expectedType\".";
|
|
||||||
}
|
|
||||||
|
|
||||||
public $varDefMap;
|
public $varDefMap;
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
@ -28,50 +27,77 @@ class VariablesInAllowedPosition extends AbstractValidationRule
|
|||||||
'enter' => function () {
|
'enter' => function () {
|
||||||
$this->varDefMap = [];
|
$this->varDefMap = [];
|
||||||
},
|
},
|
||||||
'leave' => function(OperationDefinitionNode $operation) use ($context) {
|
'leave' => function (OperationDefinitionNode $operation) use ($context) {
|
||||||
$usages = $context->getRecursiveVariableUsages($operation);
|
$usages = $context->getRecursiveVariableUsages($operation);
|
||||||
|
|
||||||
foreach ($usages as $usage) {
|
foreach ($usages as $usage) {
|
||||||
$node = $usage['node'];
|
$node = $usage['node'];
|
||||||
$type = $usage['type'];
|
$type = $usage['type'];
|
||||||
$varName = $node->name->value;
|
$varName = $node->name->value;
|
||||||
$varDef = isset($this->varDefMap[$varName]) ? $this->varDefMap[$varName] : null;
|
$varDef = $this->varDefMap[$varName] ?? null;
|
||||||
|
|
||||||
if ($varDef && $type) {
|
if (! $varDef || ! $type) {
|
||||||
// A var type is allowed if it is the same or more strict (e.g. is
|
continue;
|
||||||
// a subtype of) than the expected type. It can be more strict if
|
|
||||||
// the variable type is non-null when the expected type is nullable.
|
|
||||||
// If both are list types, the variable item type can be more strict
|
|
||||||
// than the expected item type (contravariant).
|
|
||||||
$schema = $context->getSchema();
|
|
||||||
$varType = TypeInfo::typeFromAST($schema, $varDef->type);
|
|
||||||
|
|
||||||
if ($varType && !TypeComparators::isTypeSubTypeOf($schema, $this->effectiveType($varType, $varDef), $type)) {
|
|
||||||
$context->reportError(new Error(
|
|
||||||
self::badVarPosMessage($varName, $varType, $type),
|
|
||||||
[$varDef, $node]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A var type is allowed if it is the same or more strict (e.g. is
|
||||||
|
// a subtype of) than the expected type. It can be more strict if
|
||||||
|
// the variable type is non-null when the expected type is nullable.
|
||||||
|
// If both are list types, the variable item type can be more strict
|
||||||
|
// than the expected item type (contravariant).
|
||||||
|
$schema = $context->getSchema();
|
||||||
|
$varType = TypeInfo::typeFromAST($schema, $varDef->type);
|
||||||
|
|
||||||
|
if (! $varType || TypeComparators::isTypeSubTypeOf(
|
||||||
|
$schema,
|
||||||
|
$this->effectiveType($varType, $varDef),
|
||||||
|
$type
|
||||||
|
)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->reportError(new Error(
|
||||||
|
self::badVarPosMessage($varName, $varType, $type),
|
||||||
|
[$varDef, $node]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) {
|
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) {
|
||||||
$this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
|
$this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// A var type is allowed if it is the same or more strict than the expected
|
private function effectiveType($varType, $varDef)
|
||||||
// type. It can be more strict if the variable type is non-null when the
|
{
|
||||||
// expected type is nullable. If both are list types, the variable item type can
|
return (! $varDef->defaultValue || $varType instanceof NonNull) ? $varType : new NonNull($varType);
|
||||||
// be more strict than the expected item type.
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* expected type is nullable. If both are list types, the variable item type can
|
||||||
|
* be more strict than the expected item type.
|
||||||
|
*/
|
||||||
|
public static function badVarPosMessage($varName, $varType, $expectedType)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'Variable "$%s" of type "%s" used in position expecting type "%s".',
|
||||||
|
$varName,
|
||||||
|
$varType,
|
||||||
|
$expectedType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If a variable definition has a default value, it's effectively non-null. */
|
||||||
private function varTypeAllowedForType($varType, $expectedType)
|
private function varTypeAllowedForType($varType, $expectedType)
|
||||||
{
|
{
|
||||||
if ($expectedType instanceof NonNull) {
|
if ($expectedType instanceof NonNull) {
|
||||||
if ($varType instanceof NonNull) {
|
if ($varType instanceof NonNull) {
|
||||||
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
|
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($varType instanceof NonNull) {
|
if ($varType instanceof NonNull) {
|
||||||
@ -80,13 +106,7 @@ class VariablesInAllowedPosition extends AbstractValidationRule
|
|||||||
if ($varType instanceof ListOfType && $expectedType instanceof ListOfType) {
|
if ($varType instanceof ListOfType && $expectedType instanceof ListOfType) {
|
||||||
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
|
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $varType === $expectedType;
|
return $varType === $expectedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a variable definition has a default value, it's effectively non-null.
|
|
||||||
private function effectiveType($varType, $varDef)
|
|
||||||
{
|
|
||||||
return (!$varDef->defaultValue || $varType instanceof NonNull) ? $varType : new NonNull($varType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Validator;
|
||||||
|
|
||||||
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\HasSelectionSet;
|
use GraphQL\Language\AST\HasSelectionSet;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\AST\VariableNode;
|
use GraphQL\Language\AST\VariableNode;
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use \SplObjectStorage;
|
|
||||||
use GraphQL\Error\Error;
|
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
|
||||||
use GraphQL\Type\Definition\CompositeType;
|
|
||||||
use GraphQL\Type\Definition\FieldDefinition;
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
use GraphQL\Type\Definition\InputType;
|
use GraphQL\Type\Definition\InputType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
use SplObjectStorage;
|
||||||
|
use function array_pop;
|
||||||
|
use function call_user_func_array;
|
||||||
|
use function count;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An instance of this class is passed as the "this" context to all validators,
|
* An instance of this class is passed as the "this" context to all validators,
|
||||||
@ -25,74 +30,46 @@ use GraphQL\Utils\TypeInfo;
|
|||||||
*/
|
*/
|
||||||
class ValidationContext
|
class ValidationContext
|
||||||
{
|
{
|
||||||
/**
|
/** @var Schema */
|
||||||
* @var Schema
|
|
||||||
*/
|
|
||||||
private $schema;
|
private $schema;
|
||||||
|
|
||||||
/**
|
/** @var DocumentNode */
|
||||||
* @var DocumentNode
|
|
||||||
*/
|
|
||||||
private $ast;
|
private $ast;
|
||||||
|
|
||||||
/**
|
/** @var TypeInfo */
|
||||||
* @var TypeInfo
|
|
||||||
*/
|
|
||||||
private $typeInfo;
|
private $typeInfo;
|
||||||
|
|
||||||
/**
|
/** @var Error[] */
|
||||||
* @var Error[]
|
|
||||||
*/
|
|
||||||
private $errors;
|
private $errors;
|
||||||
|
|
||||||
/**
|
/** @var FragmentDefinitionNode[] */
|
||||||
* @var FragmentDefinitionNode[]
|
|
||||||
*/
|
|
||||||
private $fragments;
|
private $fragments;
|
||||||
|
|
||||||
/**
|
/** @var SplObjectStorage */
|
||||||
* @var SplObjectStorage
|
|
||||||
*/
|
|
||||||
private $fragmentSpreads;
|
private $fragmentSpreads;
|
||||||
|
|
||||||
/**
|
/** @var SplObjectStorage */
|
||||||
* @var SplObjectStorage
|
|
||||||
*/
|
|
||||||
private $recursivelyReferencedFragments;
|
private $recursivelyReferencedFragments;
|
||||||
|
|
||||||
/**
|
/** @var SplObjectStorage */
|
||||||
* @var SplObjectStorage
|
|
||||||
*/
|
|
||||||
private $variableUsages;
|
private $variableUsages;
|
||||||
|
|
||||||
/**
|
/** @var SplObjectStorage */
|
||||||
* @var SplObjectStorage
|
|
||||||
*/
|
|
||||||
private $recursiveVariableUsages;
|
private $recursiveVariableUsages;
|
||||||
|
|
||||||
/**
|
public function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
|
||||||
* ValidationContext constructor.
|
|
||||||
*
|
|
||||||
* @param Schema $schema
|
|
||||||
* @param DocumentNode $ast
|
|
||||||
* @param TypeInfo $typeInfo
|
|
||||||
*/
|
|
||||||
function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
|
|
||||||
{
|
{
|
||||||
$this->schema = $schema;
|
$this->schema = $schema;
|
||||||
$this->ast = $ast;
|
$this->ast = $ast;
|
||||||
$this->typeInfo = $typeInfo;
|
$this->typeInfo = $typeInfo;
|
||||||
$this->errors = [];
|
$this->errors = [];
|
||||||
$this->fragmentSpreads = new SplObjectStorage();
|
$this->fragmentSpreads = new SplObjectStorage();
|
||||||
$this->recursivelyReferencedFragments = new SplObjectStorage();
|
$this->recursivelyReferencedFragments = new SplObjectStorage();
|
||||||
$this->variableUsages = new SplObjectStorage();
|
$this->variableUsages = new SplObjectStorage();
|
||||||
$this->recursiveVariableUsages = new SplObjectStorage();
|
$this->recursiveVariableUsages = new SplObjectStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function reportError(Error $error)
|
||||||
* @param Error $error
|
|
||||||
*/
|
|
||||||
function reportError(Error $error)
|
|
||||||
{
|
{
|
||||||
$this->errors[] = $error;
|
$this->errors[] = $error;
|
||||||
}
|
}
|
||||||
@ -100,7 +77,7 @@ class ValidationContext
|
|||||||
/**
|
/**
|
||||||
* @return Error[]
|
* @return Error[]
|
||||||
*/
|
*/
|
||||||
function getErrors()
|
public function getErrors()
|
||||||
{
|
{
|
||||||
return $this->errors;
|
return $this->errors;
|
||||||
}
|
}
|
||||||
@ -108,159 +85,175 @@ class ValidationContext
|
|||||||
/**
|
/**
|
||||||
* @return Schema
|
* @return Schema
|
||||||
*/
|
*/
|
||||||
function getSchema()
|
public function getSchema()
|
||||||
{
|
{
|
||||||
return $this->schema;
|
return $this->schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return DocumentNode
|
* @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
|
||||||
*/
|
*/
|
||||||
function getDocument()
|
public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
|
||||||
{
|
{
|
||||||
return $this->ast;
|
$usages = $this->recursiveVariableUsages[$operation] ?? null;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (! $usages) {
|
||||||
* @param string $name
|
$usages = $this->getVariableUsages($operation);
|
||||||
* @return FragmentDefinitionNode|null
|
|
||||||
*/
|
|
||||||
function getFragment($name)
|
|
||||||
{
|
|
||||||
$fragments = $this->fragments;
|
|
||||||
if (!$fragments) {
|
|
||||||
$fragments = [];
|
|
||||||
foreach ($this->getDocument()->definitions as $statement) {
|
|
||||||
if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) {
|
|
||||||
$fragments[$statement->name->value] = $statement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->fragments = $fragments;
|
|
||||||
}
|
|
||||||
return isset($fragments[$name]) ? $fragments[$name] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param HasSelectionSet $node
|
|
||||||
* @return FragmentSpreadNode[]
|
|
||||||
*/
|
|
||||||
function getFragmentSpreads(HasSelectionSet $node)
|
|
||||||
{
|
|
||||||
$spreads = isset($this->fragmentSpreads[$node]) ? $this->fragmentSpreads[$node] : null;
|
|
||||||
if (!$spreads) {
|
|
||||||
$spreads = [];
|
|
||||||
$setsToVisit = [$node->selectionSet];
|
|
||||||
while (!empty($setsToVisit)) {
|
|
||||||
$set = array_pop($setsToVisit);
|
|
||||||
|
|
||||||
for ($i = 0; $i < count($set->selections); $i++) {
|
|
||||||
$selection = $set->selections[$i];
|
|
||||||
if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
|
|
||||||
$spreads[] = $selection;
|
|
||||||
} else if ($selection->selectionSet) {
|
|
||||||
$setsToVisit[] = $selection->selectionSet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->fragmentSpreads[$node] = $spreads;
|
|
||||||
}
|
|
||||||
return $spreads;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OperationDefinitionNode $operation
|
|
||||||
* @return FragmentDefinitionNode[]
|
|
||||||
*/
|
|
||||||
function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
|
|
||||||
{
|
|
||||||
$fragments = isset($this->recursivelyReferencedFragments[$operation]) ? $this->recursivelyReferencedFragments[$operation] : null;
|
|
||||||
|
|
||||||
if (!$fragments) {
|
|
||||||
$fragments = [];
|
|
||||||
$collectedNames = [];
|
|
||||||
$nodesToVisit = [$operation];
|
|
||||||
while (!empty($nodesToVisit)) {
|
|
||||||
$node = array_pop($nodesToVisit);
|
|
||||||
$spreads = $this->getFragmentSpreads($node);
|
|
||||||
for ($i = 0; $i < count($spreads); $i++) {
|
|
||||||
$fragName = $spreads[$i]->name->value;
|
|
||||||
|
|
||||||
if (empty($collectedNames[$fragName])) {
|
|
||||||
$collectedNames[$fragName] = true;
|
|
||||||
$fragment = $this->getFragment($fragName);
|
|
||||||
if ($fragment) {
|
|
||||||
$fragments[] = $fragment;
|
|
||||||
$nodesToVisit[] = $fragment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->recursivelyReferencedFragments[$operation] = $fragments;
|
|
||||||
}
|
|
||||||
return $fragments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param HasSelectionSet $node
|
|
||||||
* @return array List of ['node' => VariableNode, 'type' => ?InputObjectType]
|
|
||||||
*/
|
|
||||||
function getVariableUsages(HasSelectionSet $node)
|
|
||||||
{
|
|
||||||
$usages = isset($this->variableUsages[$node]) ? $this->variableUsages[$node] : null;
|
|
||||||
|
|
||||||
if (!$usages) {
|
|
||||||
$newUsages = [];
|
|
||||||
$typeInfo = new TypeInfo($this->schema);
|
|
||||||
Visitor::visit($node, Visitor::visitWithTypeInfo($typeInfo, [
|
|
||||||
NodeKind::VARIABLE_DEFINITION => function () {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
NodeKind::VARIABLE => function (VariableNode $variable) use (&$newUsages, $typeInfo) {
|
|
||||||
$newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
|
|
||||||
}
|
|
||||||
]));
|
|
||||||
$usages = $newUsages;
|
|
||||||
$this->variableUsages[$node] = $usages;
|
|
||||||
}
|
|
||||||
return $usages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OperationDefinitionNode $operation
|
|
||||||
* @return array List of ['node' => VariableNode, 'type' => ?InputObjectType]
|
|
||||||
*/
|
|
||||||
function getRecursiveVariableUsages(OperationDefinitionNode $operation)
|
|
||||||
{
|
|
||||||
$usages = isset($this->recursiveVariableUsages[$operation]) ? $this->recursiveVariableUsages[$operation] : null;
|
|
||||||
|
|
||||||
if (!$usages) {
|
|
||||||
$usages = $this->getVariableUsages($operation);
|
|
||||||
$fragments = $this->getRecursivelyReferencedFragments($operation);
|
$fragments = $this->getRecursivelyReferencedFragments($operation);
|
||||||
|
|
||||||
$tmp = [$usages];
|
$tmp = [$usages];
|
||||||
for ($i = 0; $i < count($fragments); $i++) {
|
for ($i = 0; $i < count($fragments); $i++) {
|
||||||
$tmp[] = $this->getVariableUsages($fragments[$i]);
|
$tmp[] = $this->getVariableUsages($fragments[$i]);
|
||||||
}
|
}
|
||||||
$usages = call_user_func_array('array_merge', $tmp);
|
$usages = call_user_func_array('array_merge', $tmp);
|
||||||
$this->recursiveVariableUsages[$operation] = $usages;
|
$this->recursiveVariableUsages[$operation] = $usages;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $usages;
|
return $usages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
|
||||||
|
*/
|
||||||
|
private function getVariableUsages(HasSelectionSet $node)
|
||||||
|
{
|
||||||
|
$usages = $this->variableUsages[$node] ?? null;
|
||||||
|
|
||||||
|
if (! $usages) {
|
||||||
|
$newUsages = [];
|
||||||
|
$typeInfo = new TypeInfo($this->schema);
|
||||||
|
Visitor::visit(
|
||||||
|
$node,
|
||||||
|
Visitor::visitWithTypeInfo(
|
||||||
|
$typeInfo,
|
||||||
|
[
|
||||||
|
NodeKind::VARIABLE_DEFINITION => function () {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
NodeKind::VARIABLE => function (VariableNode $variable) use (
|
||||||
|
&$newUsages,
|
||||||
|
$typeInfo
|
||||||
|
) {
|
||||||
|
$newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$usages = $newUsages;
|
||||||
|
$this->variableUsages[$node] = $usages;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $usages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FragmentDefinitionNode[]
|
||||||
|
*/
|
||||||
|
public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
|
||||||
|
{
|
||||||
|
$fragments = $this->recursivelyReferencedFragments[$operation] ?? null;
|
||||||
|
|
||||||
|
if (! $fragments) {
|
||||||
|
$fragments = [];
|
||||||
|
$collectedNames = [];
|
||||||
|
$nodesToVisit = [$operation];
|
||||||
|
while (! empty($nodesToVisit)) {
|
||||||
|
$node = array_pop($nodesToVisit);
|
||||||
|
$spreads = $this->getFragmentSpreads($node);
|
||||||
|
for ($i = 0; $i < count($spreads); $i++) {
|
||||||
|
$fragName = $spreads[$i]->name->value;
|
||||||
|
|
||||||
|
if (! empty($collectedNames[$fragName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$collectedNames[$fragName] = true;
|
||||||
|
$fragment = $this->getFragment($fragName);
|
||||||
|
if (! $fragment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fragments[] = $fragment;
|
||||||
|
$nodesToVisit[] = $fragment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->recursivelyReferencedFragments[$operation] = $fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FragmentSpreadNode[]
|
||||||
|
*/
|
||||||
|
public function getFragmentSpreads(HasSelectionSet $node)
|
||||||
|
{
|
||||||
|
$spreads = $this->fragmentSpreads[$node] ?? null;
|
||||||
|
if (! $spreads) {
|
||||||
|
$spreads = [];
|
||||||
|
$setsToVisit = [$node->selectionSet];
|
||||||
|
while (! empty($setsToVisit)) {
|
||||||
|
$set = array_pop($setsToVisit);
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($set->selections); $i++) {
|
||||||
|
$selection = $set->selections[$i];
|
||||||
|
if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
|
||||||
|
$spreads[] = $selection;
|
||||||
|
} elseif ($selection->selectionSet) {
|
||||||
|
$setsToVisit[] = $selection->selectionSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->fragmentSpreads[$node] = $spreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $spreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @return FragmentDefinitionNode|null
|
||||||
|
*/
|
||||||
|
public function getFragment($name)
|
||||||
|
{
|
||||||
|
$fragments = $this->fragments;
|
||||||
|
if (! $fragments) {
|
||||||
|
$fragments = [];
|
||||||
|
foreach ($this->getDocument()->definitions as $statement) {
|
||||||
|
if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fragments[$statement->name->value] = $statement;
|
||||||
|
}
|
||||||
|
$this->fragments = $fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fragments[$name] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DocumentNode
|
||||||
|
*/
|
||||||
|
public function getDocument()
|
||||||
|
{
|
||||||
|
return $this->ast;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns OutputType
|
* Returns OutputType
|
||||||
*
|
*
|
||||||
* @return Type
|
* @return Type
|
||||||
*/
|
*/
|
||||||
function getType()
|
public function getType()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getType();
|
return $this->typeInfo->getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return CompositeType
|
* @return Type
|
||||||
*/
|
*/
|
||||||
function getParentType()
|
public function getParentType()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getParentType();
|
return $this->typeInfo->getParentType();
|
||||||
}
|
}
|
||||||
@ -268,7 +261,7 @@ class ValidationContext
|
|||||||
/**
|
/**
|
||||||
* @return InputType
|
* @return InputType
|
||||||
*/
|
*/
|
||||||
function getInputType()
|
public function getInputType()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getInputType();
|
return $this->typeInfo->getInputType();
|
||||||
}
|
}
|
||||||
@ -276,7 +269,7 @@ class ValidationContext
|
|||||||
/**
|
/**
|
||||||
* @return InputType
|
* @return InputType
|
||||||
*/
|
*/
|
||||||
function getParentInputType()
|
public function getParentInputType()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getParentInputType();
|
return $this->typeInfo->getParentInputType();
|
||||||
}
|
}
|
||||||
@ -284,17 +277,17 @@ class ValidationContext
|
|||||||
/**
|
/**
|
||||||
* @return FieldDefinition
|
* @return FieldDefinition
|
||||||
*/
|
*/
|
||||||
function getFieldDef()
|
public function getFieldDef()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getFieldDef();
|
return $this->typeInfo->getFieldDef();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDirective()
|
public function getDirective()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getDirective();
|
return $this->typeInfo->getDirective();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArgument()
|
public function getArgument()
|
||||||
{
|
{
|
||||||
return $this->typeInfo->getArgument();
|
return $this->typeInfo->getArgument();
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use GraphQL\Error\FormattedError;
|
|||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
use GraphQL\Validator\Rules\AbstractQuerySecurity;
|
use GraphQL\Validator\Rules\QuerySecurityRule;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
abstract class QuerySecurityTestCase extends TestCase
|
abstract class QuerySecurityTestCase extends TestCase
|
||||||
@ -13,7 +13,7 @@ abstract class QuerySecurityTestCase extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @param $max
|
* @param $max
|
||||||
*
|
*
|
||||||
* @return AbstractQuerySecurity
|
* @return QuerySecurityRule
|
||||||
*/
|
*/
|
||||||
abstract protected function getRule($max);
|
abstract protected function getRule($max);
|
||||||
|
|
||||||
@ -89,8 +89,9 @@ abstract class QuerySecurityTestCase extends TestCase
|
|||||||
{
|
{
|
||||||
$this->assertDocumentValidator($query, $maxExpected);
|
$this->assertDocumentValidator($query, $maxExpected);
|
||||||
$newMax = $maxExpected - 1;
|
$newMax = $maxExpected - 1;
|
||||||
if ($newMax !== AbstractQuerySecurity::DISABLED) {
|
if ($newMax === QuerySecurityRule::DISABLED) {
|
||||||
$this->assertDocumentValidator($query, $newMax, [$this->createFormattedError($newMax, $maxExpected)]);
|
return;
|
||||||
}
|
}
|
||||||
|
$this->assertDocumentValidator($query, $newMax, [$this->createFormattedError($newMax, $maxExpected)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user