2015-07-15 23:05:46 +06:00
|
|
|
<?php
|
|
|
|
namespace GraphQL\Validator;
|
|
|
|
|
2017-08-17 02:15:49 +07:00
|
|
|
use GraphQL\Error\Error;
|
2016-11-19 06:12:18 +07:00
|
|
|
use GraphQL\Language\AST\ListValueNode;
|
|
|
|
use GraphQL\Language\AST\DocumentNode;
|
2016-11-19 17:31:47 +07:00
|
|
|
use GraphQL\Language\AST\NodeKind;
|
2016-11-19 06:12:18 +07:00
|
|
|
use GraphQL\Language\AST\NullValueNode;
|
|
|
|
use GraphQL\Language\AST\VariableNode;
|
2016-04-25 03:57:09 +06:00
|
|
|
use GraphQL\Language\Printer;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Language\Visitor;
|
2017-08-12 21:32:07 +07:00
|
|
|
use GraphQL\Type\Schema;
|
2018-02-09 11:26:22 +01:00
|
|
|
use GraphQL\Type\Definition\EnumType;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Type\Definition\InputObjectType;
|
|
|
|
use GraphQL\Type\Definition\ListOfType;
|
|
|
|
use GraphQL\Type\Definition\NonNull;
|
|
|
|
use GraphQL\Type\Definition\Type;
|
2018-02-09 11:26:22 +01:00
|
|
|
use GraphQL\Type\Definition\ScalarType;
|
2017-07-10 19:50:26 +07:00
|
|
|
use GraphQL\Utils\Utils;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Utils\TypeInfo;
|
2017-08-18 20:48:27 +07:00
|
|
|
use GraphQL\Validator\Rules\AbstractValidationRule;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Validator\Rules\ArgumentsOfCorrectType;
|
|
|
|
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
|
2017-06-17 14:51:38 +02:00
|
|
|
use GraphQL\Validator\Rules\DisableIntrospection;
|
2018-02-11 13:27:26 +01:00
|
|
|
use GraphQL\Validator\Rules\ExecutableDefinitions;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
|
|
|
use GraphQL\Validator\Rules\FragmentsOnCompositeTypes;
|
|
|
|
use GraphQL\Validator\Rules\KnownArgumentNames;
|
|
|
|
use GraphQL\Validator\Rules\KnownDirectives;
|
|
|
|
use GraphQL\Validator\Rules\KnownFragmentNames;
|
|
|
|
use GraphQL\Validator\Rules\KnownTypeNames;
|
2016-04-25 03:57:09 +06:00
|
|
|
use GraphQL\Validator\Rules\LoneAnonymousOperation;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Validator\Rules\NoFragmentCycles;
|
|
|
|
use GraphQL\Validator\Rules\NoUndefinedVariables;
|
|
|
|
use GraphQL\Validator\Rules\NoUnusedFragments;
|
|
|
|
use GraphQL\Validator\Rules\NoUnusedVariables;
|
|
|
|
use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged;
|
|
|
|
use GraphQL\Validator\Rules\PossibleFragmentSpreads;
|
2015-08-17 20:01:55 +06:00
|
|
|
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
|
2016-04-09 10:04:14 +02:00
|
|
|
use GraphQL\Validator\Rules\QueryComplexity;
|
|
|
|
use GraphQL\Validator\Rules\QueryDepth;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Validator\Rules\ScalarLeafs;
|
2016-04-25 19:29:17 +06:00
|
|
|
use GraphQL\Validator\Rules\UniqueArgumentNames;
|
2016-11-19 00:21:56 +07:00
|
|
|
use GraphQL\Validator\Rules\UniqueDirectivesPerLocation;
|
2016-04-25 19:29:17 +06:00
|
|
|
use GraphQL\Validator\Rules\UniqueFragmentNames;
|
|
|
|
use GraphQL\Validator\Rules\UniqueInputFieldNames;
|
|
|
|
use GraphQL\Validator\Rules\UniqueOperationNames;
|
|
|
|
use GraphQL\Validator\Rules\UniqueVariableNames;
|
2015-07-15 23:05:46 +06:00
|
|
|
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
|
|
|
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
|
|
|
|
2017-08-19 23:01:46 +07:00
|
|
|
/**
|
|
|
|
* Implements the "Validation" section of the spec.
|
|
|
|
*
|
|
|
|
* Validation runs synchronously, returning an array of encountered errors, or
|
|
|
|
* 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
|
|
|
|
* default list of rules defined by the GraphQL specification will be used.
|
|
|
|
*
|
|
|
|
* Each validation rule is an instance of GraphQL\Validator\Rules\AbstractValidationRule
|
2017-08-20 02:33:31 +07:00
|
|
|
* which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
|
2017-08-19 23:01:46 +07:00
|
|
|
*
|
2017-08-20 02:33:31 +07:00
|
|
|
* Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
|
2017-08-19 23:01:46 +07:00
|
|
|
* or array of such instances when invalid.
|
|
|
|
*
|
|
|
|
* Optionally a custom TypeInfo instance may be provided. If not provided, one
|
|
|
|
* will be created from the provided schema.
|
|
|
|
*/
|
2015-07-15 23:05:46 +06:00
|
|
|
class DocumentValidator
|
|
|
|
{
|
2016-04-09 08:44:57 +02:00
|
|
|
private static $rules = [];
|
2015-07-15 23:05:46 +06:00
|
|
|
|
2016-04-09 08:44:57 +02:00
|
|
|
private static $defaultRules;
|
|
|
|
|
2017-08-18 20:48:27 +07:00
|
|
|
private static $securityRules;
|
|
|
|
|
2016-04-09 08:44:57 +02:00
|
|
|
private static $initRules = false;
|
|
|
|
|
2017-08-17 02:15:49 +07:00
|
|
|
/**
|
2017-08-19 23:01:46 +07:00
|
|
|
* Primary method for query validation. See class description for details.
|
2017-08-17 02:15:49 +07:00
|
|
|
*
|
2017-08-19 23:01:46 +07:00
|
|
|
* @api
|
|
|
|
* @param Schema $schema
|
|
|
|
* @param DocumentNode $ast
|
|
|
|
* @param AbstractValidationRule[]|null $rules
|
|
|
|
* @param TypeInfo|null $typeInfo
|
|
|
|
* @return Error[]
|
|
|
|
*/
|
|
|
|
public static function validate(
|
|
|
|
Schema $schema,
|
|
|
|
DocumentNode $ast,
|
|
|
|
array $rules = null,
|
|
|
|
TypeInfo $typeInfo = null
|
|
|
|
)
|
|
|
|
{
|
|
|
|
if (null === $rules) {
|
|
|
|
$rules = static::allRules();
|
|
|
|
}
|
|
|
|
$typeInfo = $typeInfo ?: new TypeInfo($schema);
|
|
|
|
$errors = static::visitUsingRules($schema, $typeInfo, $ast, $rules);
|
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all global validation rules.
|
|
|
|
*
|
|
|
|
* @api
|
|
|
|
* @return AbstractValidationRule[]
|
2017-08-17 02:15:49 +07:00
|
|
|
*/
|
2016-04-09 08:44:57 +02:00
|
|
|
public static function allRules()
|
2015-07-15 23:05:46 +06:00
|
|
|
{
|
2016-04-09 08:44:57 +02:00
|
|
|
if (!self::$initRules) {
|
2017-08-18 20:48:27 +07:00
|
|
|
static::$rules = array_merge(static::defaultRules(), self::securityRules(), self::$rules);
|
|
|
|
static::$initRules = true;
|
2016-04-09 08:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return self::$rules;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function defaultRules()
|
|
|
|
{
|
|
|
|
if (null === self::$defaultRules) {
|
|
|
|
self::$defaultRules = [
|
2018-02-11 13:27:26 +01:00
|
|
|
ExecutableDefinitions::class => new ExecutableDefinitions(),
|
2017-08-18 20:48:27 +07:00
|
|
|
UniqueOperationNames::class => new UniqueOperationNames(),
|
|
|
|
LoneAnonymousOperation::class => new LoneAnonymousOperation(),
|
|
|
|
KnownTypeNames::class => new KnownTypeNames(),
|
|
|
|
FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
|
|
|
|
VariablesAreInputTypes::class => new VariablesAreInputTypes(),
|
|
|
|
ScalarLeafs::class => new ScalarLeafs(),
|
|
|
|
FieldsOnCorrectType::class => new FieldsOnCorrectType(),
|
|
|
|
UniqueFragmentNames::class => new UniqueFragmentNames(),
|
|
|
|
KnownFragmentNames::class => new KnownFragmentNames(),
|
|
|
|
NoUnusedFragments::class => new NoUnusedFragments(),
|
|
|
|
PossibleFragmentSpreads::class => new PossibleFragmentSpreads(),
|
|
|
|
NoFragmentCycles::class => new NoFragmentCycles(),
|
|
|
|
UniqueVariableNames::class => new UniqueVariableNames(),
|
|
|
|
NoUndefinedVariables::class => new NoUndefinedVariables(),
|
|
|
|
NoUnusedVariables::class => new NoUnusedVariables(),
|
|
|
|
KnownDirectives::class => new KnownDirectives(),
|
|
|
|
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
|
|
|
|
KnownArgumentNames::class => new KnownArgumentNames(),
|
|
|
|
UniqueArgumentNames::class => new UniqueArgumentNames(),
|
|
|
|
ArgumentsOfCorrectType::class => new ArgumentsOfCorrectType(),
|
|
|
|
ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
|
|
|
|
DefaultValuesOfCorrectType::class => new DefaultValuesOfCorrectType(),
|
|
|
|
VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
|
|
|
|
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
|
|
|
|
UniqueInputFieldNames::class => new UniqueInputFieldNames(),
|
2015-07-15 23:05:46 +06:00
|
|
|
];
|
|
|
|
}
|
2016-04-09 08:44:57 +02:00
|
|
|
|
|
|
|
return self::$defaultRules;
|
|
|
|
}
|
|
|
|
|
2017-08-18 20:48:27 +07:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function securityRules()
|
|
|
|
{
|
|
|
|
// This way of defining rules is deprecated
|
|
|
|
// When custom security rule is required - it should be just added via DocumentValidator::addRule();
|
|
|
|
// TODO: deprecate this
|
|
|
|
|
|
|
|
if (null === self::$securityRules) {
|
|
|
|
self::$securityRules = [
|
|
|
|
DisableIntrospection::class => new DisableIntrospection(DisableIntrospection::DISABLED), // DEFAULT DISABLED
|
|
|
|
QueryDepth::class => new QueryDepth(QueryDepth::DISABLED), // default disabled
|
|
|
|
QueryComplexity::class => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
|
|
|
|
];
|
|
|
|
}
|
|
|
|
return self::$securityRules;
|
|
|
|
}
|
|
|
|
|
2017-08-17 02:15:49 +07:00
|
|
|
/**
|
2017-08-19 23:01:46 +07:00
|
|
|
* Returns global validation rule by name. Standard rules are named by class name, so
|
|
|
|
* example usage for such rules:
|
|
|
|
*
|
|
|
|
* $rule = DocumentValidator::getRule(GraphQL\Validator\Rules\QueryComplexity::class);
|
2017-08-17 02:15:49 +07:00
|
|
|
*
|
2017-08-19 23:01:46 +07:00
|
|
|
* @api
|
2017-08-17 02:15:49 +07:00
|
|
|
* @param string $name
|
2017-08-18 20:48:27 +07:00
|
|
|
* @return AbstractValidationRule
|
2017-08-17 02:15:49 +07:00
|
|
|
*/
|
2016-04-09 08:44:57 +02:00
|
|
|
public static function getRule($name)
|
|
|
|
{
|
2016-04-09 10:04:14 +02:00
|
|
|
$rules = static::allRules();
|
|
|
|
|
2017-08-18 20:48:27 +07:00
|
|
|
if (isset($rules[$name])) {
|
|
|
|
return $rules[$name];
|
|
|
|
}
|
|
|
|
|
|
|
|
$name = "GraphQL\\Validator\\Rules\\$name";
|
2016-04-09 10:04:14 +02:00
|
|
|
return isset($rules[$name]) ? $rules[$name] : null ;
|
2016-04-09 08:44:57 +02:00
|
|
|
}
|
|
|
|
|
2017-08-17 02:15:49 +07:00
|
|
|
/**
|
2017-08-19 23:01:46 +07:00
|
|
|
* Add rule to list of global validation rules
|
2017-08-17 02:15:49 +07:00
|
|
|
*
|
2017-08-19 23:01:46 +07:00
|
|
|
* @api
|
2017-08-18 20:48:27 +07:00
|
|
|
* @param AbstractValidationRule $rule
|
2017-08-17 02:15:49 +07:00
|
|
|
*/
|
2017-08-18 20:48:27 +07:00
|
|
|
public static function addRule(AbstractValidationRule $rule)
|
2016-04-09 08:44:57 +02:00
|
|
|
{
|
2017-08-18 20:48:27 +07:00
|
|
|
self::$rules[$rule->getName()] = $rule;
|
2016-04-09 08:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function isError($value)
|
2015-07-15 23:05:46 +06:00
|
|
|
{
|
|
|
|
return is_array($value)
|
2017-06-06 12:55:38 +02:00
|
|
|
? count(array_filter($value, function($item) { return $item instanceof \Exception || $item instanceof \Throwable;})) === count($value)
|
|
|
|
: ($value instanceof \Exception || $value instanceof \Throwable);
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
2016-04-09 08:44:57 +02:00
|
|
|
public static function append(&$arr, $items)
|
2015-07-15 23:05:46 +06:00
|
|
|
{
|
|
|
|
if (is_array($items)) {
|
|
|
|
$arr = array_merge($arr, $items);
|
|
|
|
} else {
|
|
|
|
$arr[] = $items;
|
|
|
|
}
|
|
|
|
return $arr;
|
|
|
|
}
|
|
|
|
|
2016-04-25 03:57:09 +06:00
|
|
|
/**
|
|
|
|
* Utility for validators which determines if a value literal AST is valid given
|
|
|
|
* an input type.
|
|
|
|
*
|
|
|
|
* Note that this only validates literal values, variables are assumed to
|
|
|
|
* provide values of the correct type.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-11-19 06:47:55 +07:00
|
|
|
public static function isValidLiteralValue(Type $type, $valueNode)
|
2015-07-15 23:05:46 +06:00
|
|
|
{
|
2016-04-25 03:57:09 +06:00
|
|
|
// A value must be provided if the type is non-null.
|
|
|
|
if ($type instanceof NonNull) {
|
2016-11-19 06:47:55 +07:00
|
|
|
if (!$valueNode || $valueNode instanceof NullValueNode) {
|
2016-11-19 04:15:40 +07:00
|
|
|
return [ 'Expected "' . Utils::printSafe($type) . '", found null.' ];
|
2016-04-25 03:57:09 +06:00
|
|
|
}
|
2016-11-19 06:47:55 +07:00
|
|
|
return static::isValidLiteralValue($type->getWrappedType(), $valueNode);
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
2016-11-19 06:47:55 +07:00
|
|
|
if (!$valueNode || $valueNode instanceof NullValueNode) {
|
2016-04-25 03:57:09 +06:00
|
|
|
return [];
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
|
|
|
// This function only tests literals, and assumes variables will provide
|
|
|
|
// values of the correct type.
|
2016-11-19 06:47:55 +07:00
|
|
|
if ($valueNode instanceof VariableNode) {
|
2016-04-25 03:57:09 +06:00
|
|
|
return [];
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lists accept a non-list value as a list of one.
|
|
|
|
if ($type instanceof ListOfType) {
|
|
|
|
$itemType = $type->getWrappedType();
|
2016-11-19 06:47:55 +07:00
|
|
|
if ($valueNode instanceof ListValueNode) {
|
2016-04-25 03:57:09 +06:00
|
|
|
$errors = [];
|
2016-11-19 06:47:55 +07:00
|
|
|
foreach($valueNode->values as $index => $itemNode) {
|
|
|
|
$tmp = static::isValidLiteralValue($itemType, $itemNode);
|
2016-04-25 03:57:09 +06:00
|
|
|
|
|
|
|
if ($tmp) {
|
|
|
|
$errors = array_merge($errors, Utils::map($tmp, function($error) use ($index) {
|
|
|
|
return "In element #$index: $error";
|
|
|
|
}));
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
}
|
2016-04-25 03:57:09 +06:00
|
|
|
return $errors;
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
2018-02-11 13:15:51 +01:00
|
|
|
|
|
|
|
return static::isValidLiteralValue($itemType, $valueNode);
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
2016-04-25 03:57:09 +06:00
|
|
|
// Input objects check each defined field and look for undefined fields.
|
2015-07-15 23:05:46 +06:00
|
|
|
if ($type instanceof InputObjectType) {
|
2016-11-19 17:31:47 +07:00
|
|
|
if ($valueNode->kind !== NodeKind::OBJECT) {
|
2016-04-25 03:57:09 +06:00
|
|
|
return [ "Expected \"{$type->name}\", found not an object." ];
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
2016-04-25 03:57:09 +06:00
|
|
|
|
|
|
|
$fields = $type->getFields();
|
2018-02-11 13:15:51 +01:00
|
|
|
|
2016-04-25 03:57:09 +06:00
|
|
|
$errors = [];
|
|
|
|
|
|
|
|
// Ensure every provided field is defined.
|
2016-11-19 06:47:55 +07:00
|
|
|
$fieldNodes = $valueNode->fields;
|
2015-07-15 23:05:46 +06:00
|
|
|
|
2016-11-19 06:47:55 +07:00
|
|
|
foreach ($fieldNodes as $providedFieldNode) {
|
|
|
|
if (empty($fields[$providedFieldNode->name->value])) {
|
|
|
|
$errors[] = "In field \"{$providedFieldNode->name->value}\": Unknown field.";
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
}
|
2016-04-25 03:57:09 +06:00
|
|
|
|
|
|
|
// Ensure every defined field is valid.
|
2016-11-19 06:47:55 +07:00
|
|
|
$fieldNodeMap = Utils::keyMap($fieldNodes, function($fieldNode) {return $fieldNode->name->value;});
|
2016-04-25 03:57:09 +06:00
|
|
|
foreach ($fields as $fieldName => $field) {
|
|
|
|
$result = static::isValidLiteralValue(
|
|
|
|
$field->getType(),
|
2016-11-19 06:47:55 +07:00
|
|
|
isset($fieldNodeMap[$fieldName]) ? $fieldNodeMap[$fieldName]->value : null
|
2016-04-25 03:57:09 +06:00
|
|
|
);
|
|
|
|
if ($result) {
|
|
|
|
$errors = array_merge($errors, Utils::map($result, function($error) use ($fieldName) {
|
|
|
|
return "In field \"$fieldName\": $error";
|
|
|
|
}));
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
}
|
2016-04-25 03:57:09 +06:00
|
|
|
|
|
|
|
return $errors;
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
2018-02-09 11:26:22 +01:00
|
|
|
Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type');
|
2016-04-25 03:57:09 +06:00
|
|
|
|
2018-02-09 11:26:22 +01:00
|
|
|
// Scalars determine if a literal values is valid.
|
|
|
|
if (!$type->isValidLiteral($valueNode)) {
|
|
|
|
$printed = Printer::doPrint($valueNode);
|
|
|
|
return [ "Expected type \"{$type->name}\", found $printed." ];
|
2016-04-25 03:57:09 +06:00
|
|
|
}
|
|
|
|
|
2018-02-09 11:26:22 +01:00
|
|
|
return [];
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This uses a specialized visitor which runs multiple visitors in parallel,
|
|
|
|
* while maintaining the visitor skip and break API.
|
|
|
|
*
|
|
|
|
* @param Schema $schema
|
2016-04-25 03:57:09 +06:00
|
|
|
* @param TypeInfo $typeInfo
|
2016-11-19 06:47:55 +07:00
|
|
|
* @param DocumentNode $documentNode
|
2017-08-18 20:48:27 +07:00
|
|
|
* @param AbstractValidationRule[] $rules
|
2015-07-15 23:05:46 +06:00
|
|
|
* @return array
|
|
|
|
*/
|
2016-11-19 06:47:55 +07:00
|
|
|
public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules)
|
2015-07-15 23:05:46 +06:00
|
|
|
{
|
2016-11-19 06:47:55 +07:00
|
|
|
$context = new ValidationContext($schema, $documentNode, $typeInfo);
|
2016-04-25 03:57:09 +06:00
|
|
|
$visitors = [];
|
|
|
|
foreach ($rules as $rule) {
|
2017-08-18 20:48:27 +07:00
|
|
|
$visitors[] = $rule->getVisitor($context);
|
2016-04-25 03:57:09 +06:00
|
|
|
}
|
2016-11-19 06:47:55 +07:00
|
|
|
Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
|
2016-04-25 03:57:09 +06:00
|
|
|
return $context->getErrors();
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
}
|