mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 06:16:05 +03:00
Updating validator rules for april2016 spec
This commit is contained in:
parent
17081cec1c
commit
f1ddc98390
@ -2,7 +2,7 @@
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
|
||||
class FragmentDefinition extends Node implements Definition
|
||||
class FragmentDefinition extends Node implements Definition, HasSelectionSet
|
||||
{
|
||||
public $kind = Node::FRAGMENT_DEFINITION;
|
||||
|
||||
|
10
src/Language/AST/HasSelectionSet.php
Normal file
10
src/Language/AST/HasSelectionSet.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
interface HasSelectionSet
|
||||
{
|
||||
/**
|
||||
* export type Definition = OperationDefinition
|
||||
* | FragmentDefinition
|
||||
*/
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class OperationDefinition extends Node implements Definition
|
||||
class OperationDefinition extends Node implements Definition, HasSelectionSet
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
@ -2,12 +2,10 @@
|
||||
namespace GraphQL\Language;
|
||||
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Utils\TypeInfo;
|
||||
|
||||
class Visitor
|
||||
{
|
||||
const BREAK_VISIT = '@@BREAK@@';
|
||||
const CONTINUE_VISIT = '@@CONTINUE@@';
|
||||
|
||||
/**
|
||||
* Break visitor
|
||||
*
|
||||
@ -244,7 +242,7 @@ class Visitor
|
||||
throw new \Exception('Invalid AST Node: ' . json_encode($node));
|
||||
}
|
||||
|
||||
$visitFn = self::getVisitFn($visitor, $isLeaving, $node->kind);
|
||||
$visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving);
|
||||
|
||||
if ($visitFn) {
|
||||
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
|
||||
@ -308,13 +306,100 @@ class Visitor
|
||||
return $newRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $visitors
|
||||
* @return array
|
||||
*/
|
||||
static function visitInParallel($visitors)
|
||||
{
|
||||
// TODO: implement real parallel visiting once PHP supports it
|
||||
$visitorsCount = count($visitors);
|
||||
$skipping = new \SplFixedArray($visitorsCount);
|
||||
|
||||
return [
|
||||
'enter' => function ($node) use ($visitors, $skipping, $visitorsCount) {
|
||||
for ($i = 0; $i < $visitorsCount; $i++) {
|
||||
if (empty($skipping[$i])) {
|
||||
$fn = self::getVisitFn($visitors[$i], $node->kind, /* isLeaving */ false);
|
||||
|
||||
if ($fn) {
|
||||
$result = call_user_func_array($fn, func_get_args());
|
||||
|
||||
if ($result instanceof VisitorOperation) {
|
||||
if ($result->doContinue) {
|
||||
$skipping[$i] = $node;
|
||||
} else if ($result->doBreak) {
|
||||
$skipping[$i] = $result;
|
||||
}
|
||||
} else if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'leave' => function ($node) use ($visitors, $skipping, $visitorsCount) {
|
||||
for ($i = 0; $i < $visitorsCount; $i++) {
|
||||
if (empty($skipping[$i])) {
|
||||
$fn = self::getVisitFn($visitors[$i], $node->kind, /* isLeaving */ true);
|
||||
|
||||
if ($fn) {
|
||||
$result = call_user_func_array($fn, func_get_args());
|
||||
if ($result instanceof VisitorOperation) {
|
||||
if ($result->doBreak) {
|
||||
$skipping[$i] = $result;
|
||||
}
|
||||
} else if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
} else if ($skipping[$i] === $node) {
|
||||
$skipping[$i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new visitor instance which maintains a provided TypeInfo instance
|
||||
* along with visiting visitor.
|
||||
*/
|
||||
static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor)
|
||||
{
|
||||
return [
|
||||
'enter' => function ($node) use ($typeInfo, $visitor) {
|
||||
$typeInfo->enter($node);
|
||||
$fn = self::getVisitFn($visitor, $node->kind, false);
|
||||
|
||||
if ($fn) {
|
||||
$result = call_user_func_array($fn, func_get_args());
|
||||
if ($result) {
|
||||
$typeInfo->leave($node);
|
||||
if ($result instanceof Node) {
|
||||
$typeInfo->enter($result);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
'leave' => function ($node) use ($typeInfo, $visitor) {
|
||||
$fn = self::getVisitFn($visitor, $node->kind, true);
|
||||
$result = $fn ? call_user_func_array($fn, func_get_args()) : null;
|
||||
$typeInfo->leave($node);
|
||||
return $result;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $visitor
|
||||
* @param $isLeaving
|
||||
* @param $kind
|
||||
* @param $isLeaving
|
||||
* @return null
|
||||
*/
|
||||
public static function getVisitFn($visitor, $isLeaving, $kind)
|
||||
public static function getVisitFn($visitor, $kind, $isLeaving)
|
||||
{
|
||||
if (!$visitor) {
|
||||
return null;
|
||||
|
241
src/Schema.php
241
src/Schema.php
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\AbstractType;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\FieldArgument;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
@ -16,37 +17,128 @@ use GraphQL\Type\Introspection;
|
||||
|
||||
class Schema
|
||||
{
|
||||
protected $querySchema;
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
protected $_queryType;
|
||||
|
||||
protected $mutationSchema;
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
protected $_mutationType;
|
||||
|
||||
protected $subscriptionSchema;
|
||||
|
||||
protected $_typeMap;
|
||||
/**
|
||||
* @var ObjectType
|
||||
*/
|
||||
protected $_subscriptionType;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
protected $_directives;
|
||||
|
||||
public function __construct(Type $querySchema = null, Type $mutationSchema = null, Type $subscriptionSchema = null)
|
||||
{
|
||||
Utils::invariant($querySchema || $mutationSchema, "Either query or mutation type must be set");
|
||||
$this->querySchema = $querySchema;
|
||||
$this->mutationSchema = $mutationSchema;
|
||||
$this->subscriptionSchema = $subscriptionSchema;
|
||||
/**
|
||||
* @var array<string, Type>
|
||||
*/
|
||||
protected $_typeMap;
|
||||
|
||||
InterfaceType::loadImplementationToInterfaces();
|
||||
/**
|
||||
* @var array<string, ObjectType[]>
|
||||
*/
|
||||
protected $_implementations;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, boolean>>
|
||||
*/
|
||||
protected $_possibleTypeMap;
|
||||
|
||||
/**
|
||||
* Schema constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct($config = null)
|
||||
{
|
||||
if (func_num_args() > 1 || $config instanceof Type) {
|
||||
trigger_error(
|
||||
'GraphQL\Schema constructor expects config object now instead of types passed as arguments. '.
|
||||
'See https://github.com/webonyx/graphql-php/issues/36',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
list($queryType, $mutationType, $subscriptionType) = func_get_args();
|
||||
|
||||
$config = [
|
||||
'query' => $queryType,
|
||||
'mutation' => $mutationType,
|
||||
'subscription' => $subscriptionType
|
||||
];
|
||||
}
|
||||
|
||||
$this->_init($config);
|
||||
}
|
||||
|
||||
protected function _init(array $config)
|
||||
{
|
||||
Utils::invariant(isset($config['query']) || isset($config['mutation']), "Either query or mutation type must be set");
|
||||
|
||||
$config += [
|
||||
'query' => null,
|
||||
'mutation' => null,
|
||||
'subscription' => null,
|
||||
'directives' => [],
|
||||
'validate' => true
|
||||
];
|
||||
|
||||
$this->_queryType = $config['query'];
|
||||
$this->_mutationType = $config['mutation'];
|
||||
$this->_subscriptionType = $config['subscription'];
|
||||
|
||||
$this->_directives = array_merge($config['directives'], [
|
||||
Directive::includeDirective(),
|
||||
Directive::skipDirective()
|
||||
]);
|
||||
|
||||
// Build type map now to detect any errors within this schema.
|
||||
$initialTypes = [
|
||||
$config['query'],
|
||||
$config['mutation'],
|
||||
$config['subscription'],
|
||||
Introspection::_schema()
|
||||
];
|
||||
if (!empty($config['types'])) {
|
||||
$initialTypes = array_merge($initialTypes, $config['types']);
|
||||
}
|
||||
|
||||
$map = [];
|
||||
foreach ([$this->getQueryType(), $this->getMutationType(), Introspection::_schema()] as $type) {
|
||||
foreach ($initialTypes as $type) {
|
||||
$this->_extractTypes($type, $map);
|
||||
}
|
||||
$this->_typeMap = $map + Type::getInternalTypes();
|
||||
|
||||
// Keep track of all implementations by interface name.
|
||||
$this->_implementations = [];
|
||||
foreach ($this->_typeMap as $typeName => $type) {
|
||||
if ($type instanceof ObjectType) {
|
||||
foreach ($type->getInterfaces() as $iface) {
|
||||
$this->_implementations[$iface->name][] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($config['validate']) {
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additionaly validate schema for integrity
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
// Enforce correct interface implementations
|
||||
foreach ($this->_typeMap as $typeName => $type) {
|
||||
if ($type instanceof ObjectType) {
|
||||
foreach ($type->getInterfaces() as $iface) {
|
||||
$this->assertObjectImplementsInterface($type, $iface);
|
||||
$this->_assertObjectImplementsInterface($type, $iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,7 +149,7 @@ class Schema
|
||||
* @param InterfaceType $iface
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function assertObjectImplementsInterface(ObjectType $object, InterfaceType $iface)
|
||||
protected function _assertObjectImplementsInterface(ObjectType $object, InterfaceType $iface)
|
||||
{
|
||||
$objectFieldMap = $object->getFields();
|
||||
$ifaceFieldMap = $iface->getFields();
|
||||
@ -73,7 +165,7 @@ class Schema
|
||||
$objectField = $objectFieldMap[$fieldName];
|
||||
|
||||
Utils::invariant(
|
||||
$this->isEqualType($ifaceField->getType(), $objectField->getType()),
|
||||
$this->_isEqualType($ifaceField->getType(), $objectField->getType()),
|
||||
"$iface.$fieldName expects type \"{$ifaceField->getType()}\" but " .
|
||||
"$object.$fieldName provides type \"{$objectField->getType()}"
|
||||
);
|
||||
@ -93,7 +185,7 @@ class Schema
|
||||
// Assert interface field arg type matches object field arg type.
|
||||
// (invariant)
|
||||
Utils::invariant(
|
||||
$this->isEqualType($ifaceArg->getType(), $objectArg->getType()),
|
||||
$this->_isEqualType($ifaceArg->getType(), $objectArg->getType()),
|
||||
"$iface.$fieldName($argName:) expects type \"{$ifaceArg->getType()}\" " .
|
||||
"but $object.$fieldName($argName:) provides " .
|
||||
"type \"{$objectArg->getType()}\""
|
||||
@ -118,35 +210,105 @@ class Schema
|
||||
* @param $typeB
|
||||
* @return bool
|
||||
*/
|
||||
private function isEqualType($typeA, $typeB)
|
||||
protected function _isEqualType($typeA, $typeB)
|
||||
{
|
||||
if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
|
||||
return $this->isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
return $this->_isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
|
||||
return $this->isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
return $this->_isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
return $typeA === $typeB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function getQueryType()
|
||||
{
|
||||
return $this->querySchema;
|
||||
return $this->_queryType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function getMutationType()
|
||||
{
|
||||
return $this->mutationSchema;
|
||||
return $this->_mutationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function getSubscriptionType()
|
||||
{
|
||||
return $this->subscriptionSchema;
|
||||
return $this->_subscriptionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTypeMap()
|
||||
{
|
||||
return $this->_typeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Type
|
||||
*/
|
||||
public function getType($name)
|
||||
{
|
||||
$map = $this->getTypeMap();
|
||||
return isset($map[$name]) ? $map[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractType $abstractType
|
||||
* @return ObjectType[]
|
||||
*/
|
||||
public function getPossibleTypes(AbstractType $abstractType)
|
||||
{
|
||||
if ($abstractType instanceof UnionType) {
|
||||
return $abstractType->getTypes();
|
||||
}
|
||||
Utils::invariant($abstractType instanceof InterfaceType);
|
||||
return $this->_implementations[$abstractType->name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractType $abstractType
|
||||
* @param ObjectType $possibleType
|
||||
* @return bool
|
||||
*/
|
||||
public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType)
|
||||
{
|
||||
if (null === $this->_possibleTypeMap) {
|
||||
$this->_possibleTypeMap = [];
|
||||
}
|
||||
|
||||
if (!isset($this->_possibleTypeMap[$abstractType->name])) {
|
||||
$tmp = [];
|
||||
foreach ($this->getPossibleTypes($abstractType) as $type) {
|
||||
$tmp[$type->name] = true;
|
||||
}
|
||||
$this->_possibleTypeMap[$abstractType->name] = $tmp;
|
||||
}
|
||||
|
||||
return !empty($this->_possibleTypeMap[$abstractType->name][$possibleType->name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directive[]
|
||||
*/
|
||||
public function getDirectives()
|
||||
{
|
||||
return $this->_directives;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return null
|
||||
* @return Directive
|
||||
*/
|
||||
public function getDirective($name)
|
||||
{
|
||||
@ -158,26 +320,7 @@ class Schema
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Directive>
|
||||
*/
|
||||
public function getDirectives()
|
||||
{
|
||||
if (!$this->_directives) {
|
||||
$this->_directives = [
|
||||
Directive::includeDirective(),
|
||||
Directive::skipDirective()
|
||||
];
|
||||
}
|
||||
return $this->_directives;
|
||||
}
|
||||
|
||||
public function getTypeMap()
|
||||
{
|
||||
return $this->_typeMap;
|
||||
}
|
||||
|
||||
private function _extractTypes($type, &$map)
|
||||
protected function _extractTypes($type, &$map)
|
||||
{
|
||||
if (!$type) {
|
||||
return $map;
|
||||
@ -198,8 +341,8 @@ class Schema
|
||||
|
||||
$nestedTypes = [];
|
||||
|
||||
if ($type instanceof InterfaceType || $type instanceof UnionType) {
|
||||
$nestedTypes = $type->getPossibleTypes();
|
||||
if ($type instanceof UnionType) {
|
||||
$nestedTypes = $type->getTypes();
|
||||
}
|
||||
if ($type instanceof ObjectType) {
|
||||
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
|
||||
@ -218,10 +361,4 @@ class Schema
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function getType($name)
|
||||
{
|
||||
$map = $this->getTypeMap();
|
||||
return isset($map[$name]) ? $map[$name] : null;
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ GraphQLUnionType;
|
||||
/**
|
||||
* @return array<ObjectType>
|
||||
*/
|
||||
public function getPossibleTypes();
|
||||
// public function getPossibleTypes();
|
||||
|
||||
/**
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function getObjectType($value, ResolveInfo $info);
|
||||
// public function getObjectType($value, ResolveInfo $info);
|
||||
|
||||
/**
|
||||
* @param Type $type
|
||||
* @return bool
|
||||
*/
|
||||
public function isPossibleType(Type $type);
|
||||
// public function isPossibleType(Type $type);
|
||||
}
|
||||
|
@ -5,6 +5,16 @@ class Directive
|
||||
{
|
||||
public static $internalDirectives;
|
||||
|
||||
public static $directiveLocations = [
|
||||
'QUERY' => 'QUERY',
|
||||
'MUTATION' => 'MUTATION',
|
||||
'SUBSCRIPTION' => 'SUBSCRIPTION',
|
||||
'FIELD' => 'FIELD',
|
||||
'FRAGMENT_DEFINITION' => 'FRAGMENT_DEFINITION',
|
||||
'FRAGMENT_SPREAD' => 'FRAGMENT_SPREAD',
|
||||
'INLINE_FRAGMENT' => 'INLINE_FRAGMENT',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return Directive
|
||||
*/
|
||||
@ -30,6 +40,11 @@ class Directive
|
||||
'include' => new self([
|
||||
'name' => 'include',
|
||||
'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.',
|
||||
'locations' => [
|
||||
self::$directiveLocations['FIELD'],
|
||||
self::$directiveLocations['FRAGMENT_SPREAD'],
|
||||
self::$directiveLocations['INLINE_FRAGMENT'],
|
||||
],
|
||||
'args' => [
|
||||
new FieldArgument([
|
||||
'name' => 'if',
|
||||
@ -37,23 +52,22 @@ class Directive
|
||||
'description' => 'Included when true.'
|
||||
])
|
||||
],
|
||||
'onOperation' => false,
|
||||
'onFragment' => true,
|
||||
'onField' => true
|
||||
]),
|
||||
'skip' => new self([
|
||||
'name' => 'skip',
|
||||
'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.',
|
||||
'locations' => [
|
||||
self::$directiveLocations['FIELD'],
|
||||
self::$directiveLocations['FRAGMENT_SPREAD'],
|
||||
self::$directiveLocations['INLINE_FRAGMENT']
|
||||
],
|
||||
'args' => [
|
||||
new FieldArgument([
|
||||
'name' => 'if',
|
||||
'type' => Type::nonNull(Type::boolean()),
|
||||
'description' => 'Skipped when true'
|
||||
])
|
||||
],
|
||||
'onOperation' => false,
|
||||
'onFragment' => true,
|
||||
'onField' => true
|
||||
]
|
||||
])
|
||||
];
|
||||
}
|
||||
@ -70,26 +84,18 @@ class Directive
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* Values from self::$locationMap
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $locations;
|
||||
|
||||
/**
|
||||
* @var FieldArgument[]
|
||||
*/
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $onOperation;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $onFragment;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $onField;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
foreach ($config as $key => $value) {
|
||||
|
@ -6,7 +6,7 @@ use GraphQL\Utils;
|
||||
class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
||||
{
|
||||
/**
|
||||
* @var Array<GraphQLObjectType>
|
||||
* @var ObjectType[]
|
||||
*/
|
||||
private $_types;
|
||||
|
||||
@ -48,10 +48,16 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
||||
$this->_config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<ObjectType>
|
||||
*/
|
||||
public function getPossibleTypes()
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated in favor of ' . __CLASS__ . '::getTypes()', E_USER_DEPRECATED);
|
||||
return $this->getTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectType[]
|
||||
*/
|
||||
public function getTypes()
|
||||
{
|
||||
if ($this->_types instanceof \Closure) {
|
||||
$this->_types = call_user_func($this->_types);
|
||||
@ -71,7 +77,7 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
||||
|
||||
if (null === $this->_possibleTypeNames) {
|
||||
$this->_possibleTypeNames = [];
|
||||
foreach ($this->getPossibleTypes() as $possibleType) {
|
||||
foreach ($this->getTypes() as $possibleType) {
|
||||
$this->_possibleTypeNames[$possibleType->name] = true;
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ class TypeInfo
|
||||
return $innerType ? new NonNull($innerType) : null;
|
||||
}
|
||||
|
||||
Utils::invariant($inputTypeAst->kind === Node::NAMED_TYPE, 'Must be a named type');
|
||||
Utils::invariant($inputTypeAst && $inputTypeAst->kind === Node::NAMED_TYPE, 'Must be a named type');
|
||||
return $schema->getType($inputTypeAst->name->value);
|
||||
}
|
||||
|
||||
@ -197,11 +197,7 @@ class TypeInfo
|
||||
// isCompositeType is a type refining predicate, so this is safe.
|
||||
$compositeType = $namedType;
|
||||
}
|
||||
array_push($this->_parentTypeStack, $compositeType);
|
||||
break;
|
||||
|
||||
case Node::DIRECTIVE:
|
||||
$this->_directive = $schema->getDirective($node->name->value);
|
||||
$this->_parentTypeStack[] = $compositeType; // push
|
||||
break;
|
||||
|
||||
case Node::FIELD:
|
||||
@ -210,8 +206,12 @@ class TypeInfo
|
||||
if ($parentType) {
|
||||
$fieldDef = self::_getFieldDef($schema, $parentType, $node);
|
||||
}
|
||||
array_push($this->_fieldDefStack, $fieldDef);
|
||||
array_push($this->_typeStack, $fieldDef ? $fieldDef->getType() : null);
|
||||
$this->_fieldDefStack[] = $fieldDef; // push
|
||||
$this->_typeStack[] = $fieldDef ? $fieldDef->getType() : null; // push
|
||||
break;
|
||||
|
||||
case Node::DIRECTIVE:
|
||||
$this->_directive = $schema->getDirective($node->name->value);
|
||||
break;
|
||||
|
||||
case Node::OPERATION_DEFINITION:
|
||||
@ -220,18 +220,22 @@ class TypeInfo
|
||||
$type = $schema->getQueryType();
|
||||
} else if ($node->operation === 'mutation') {
|
||||
$type = $schema->getMutationType();
|
||||
} else if ($node->operation === 'subscription') {
|
||||
$type = $schema->getSubscriptionType();
|
||||
}
|
||||
array_push($this->_typeStack, $type);
|
||||
$this->_typeStack[] = $type; // push
|
||||
break;
|
||||
|
||||
case Node::INLINE_FRAGMENT:
|
||||
case Node::FRAGMENT_DEFINITION:
|
||||
$type = self::typeFromAST($schema, $node->typeCondition);
|
||||
array_push($this->_typeStack, $type);
|
||||
$typeConditionAST = $node->typeCondition;
|
||||
$outputType = $typeConditionAST ? self::typeFromAST($schema, $typeConditionAST) : $this->getType();
|
||||
$this->_typeStack[] = $outputType; // push
|
||||
break;
|
||||
|
||||
case Node::VARIABLE_DEFINITION:
|
||||
array_push($this->_inputTypeStack, self::typeFromAST($schema, $node->type));
|
||||
$inputType = self::typeFromAST($schema, $node->type);
|
||||
$this->_inputTypeStack[] = $inputType; // push
|
||||
break;
|
||||
|
||||
case Node::ARGUMENT:
|
||||
@ -244,15 +248,12 @@ class TypeInfo
|
||||
}
|
||||
}
|
||||
$this->_argument = $argDef;
|
||||
array_push($this->_inputTypeStack, $argType);
|
||||
$this->_inputTypeStack[] = $argType; // push
|
||||
break;
|
||||
|
||||
case Node::LST:
|
||||
$listType = Type::getNullableType($this->getInputType());
|
||||
array_push(
|
||||
$this->_inputTypeStack,
|
||||
$listType instanceof ListOfType ? $listType->getWrappedType() : null
|
||||
);
|
||||
$this->_inputTypeStack[] = ($listType instanceof ListOfType ? $listType->getWrappedType() : null); // push
|
||||
break;
|
||||
|
||||
case Node::OBJECT_FIELD:
|
||||
@ -263,7 +264,7 @@ class TypeInfo
|
||||
$inputField = isset($tmp[$node->name->value]) ? $tmp[$node->name->value] : null;
|
||||
$fieldType = $inputField ? $inputField->getType() : null;
|
||||
}
|
||||
array_push($this->_inputTypeStack, $fieldType);
|
||||
$this->_inputTypeStack[] = $fieldType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,13 @@ use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\Value;
|
||||
use GraphQL\Language\AST\Variable;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Language\VisitorOperation;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
@ -27,6 +29,7 @@ use GraphQL\Validator\Rules\KnownArgumentNames;
|
||||
use GraphQL\Validator\Rules\KnownDirectives;
|
||||
use GraphQL\Validator\Rules\KnownFragmentNames;
|
||||
use GraphQL\Validator\Rules\KnownTypeNames;
|
||||
use GraphQL\Validator\Rules\LoneAnonymousOperation;
|
||||
use GraphQL\Validator\Rules\NoFragmentCycles;
|
||||
use GraphQL\Validator\Rules\NoUndefinedVariables;
|
||||
use GraphQL\Validator\Rules\NoUnusedFragments;
|
||||
@ -62,28 +65,31 @@ class DocumentValidator
|
||||
{
|
||||
if (null === self::$defaultRules) {
|
||||
self::$defaultRules = [
|
||||
// new UniqueOperationNames,
|
||||
// new LoneAnonymousOperation,
|
||||
// 'UniqueOperationNames' => new UniqueOperationNames(),
|
||||
'LoneAnonymousOperation' => new LoneAnonymousOperation(),
|
||||
'KnownTypeNames' => new KnownTypeNames(),
|
||||
'FragmentsOnCompositeTypes' => new FragmentsOnCompositeTypes(),
|
||||
'VariablesAreInputTypes' => new VariablesAreInputTypes(),
|
||||
'ScalarLeafs' => new ScalarLeafs(),
|
||||
'FieldsOnCorrectType' => new FieldsOnCorrectType(),
|
||||
// new UniqueFragmentNames,
|
||||
// 'UniqueFragmentNames' => new UniqueFragmentNames(),
|
||||
'KnownFragmentNames' => new KnownFragmentNames(),
|
||||
'NoUnusedFragments' => new NoUnusedFragments(),
|
||||
'PossibleFragmentSpreads' => new PossibleFragmentSpreads(),
|
||||
'NoFragmentCycles' => new NoFragmentCycles(),
|
||||
// 'UniqueVariableNames' => new UniqueVariableNames(),
|
||||
'NoUndefinedVariables' => new NoUndefinedVariables(),
|
||||
'NoUnusedVariables' => new NoUnusedVariables(),
|
||||
'KnownDirectives' => new KnownDirectives(),
|
||||
'KnownArgumentNames' => new KnownArgumentNames(),
|
||||
// new UniqueArgumentNames,
|
||||
// 'UniqueArgumentNames' => new UniqueArgumentNames(),
|
||||
'ArgumentsOfCorrectType' => new ArgumentsOfCorrectType(),
|
||||
'ProvidedNonNullArguments' => new ProvidedNonNullArguments(),
|
||||
'DefaultValuesOfCorrectType' => new DefaultValuesOfCorrectType(),
|
||||
'VariablesInAllowedPosition' => new VariablesInAllowedPosition(),
|
||||
'OverlappingFieldsCanBeMerged' => new OverlappingFieldsCanBeMerged(),
|
||||
// 'UniqueInputFieldNames' => new UniqueInputFieldNames(),
|
||||
|
||||
// Query Security
|
||||
'QueryDepth' => new QueryDepth(QueryDepth::DISABLED), // default disabled
|
||||
'QueryComplexity' => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
|
||||
@ -107,7 +113,8 @@ class DocumentValidator
|
||||
|
||||
public static function validate(Schema $schema, Document $ast, array $rules = null)
|
||||
{
|
||||
$errors = static::visitUsingRules($schema, $ast, $rules ?: static::allRules());
|
||||
$typeInfo = new TypeInfo($schema);
|
||||
$errors = static::visitUsingRules($schema, $typeInfo, $ast, $rules ?: static::allRules());
|
||||
return $errors;
|
||||
}
|
||||
|
||||
@ -128,76 +135,109 @@ class DocumentValidator
|
||||
return $arr;
|
||||
}
|
||||
|
||||
public static function isValidLiteralValue($valueAST, Type $type)
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function isValidLiteralValue(Type $type, $valueAST)
|
||||
{
|
||||
// A value can only be not provided if the type is nullable.
|
||||
// A value must be provided if the type is non-null.
|
||||
if ($type instanceof NonNull) {
|
||||
$wrappedType = $type->getWrappedType();
|
||||
if (!$valueAST) {
|
||||
return !($type instanceof NonNull);
|
||||
if ($wrappedType->name) {
|
||||
return [ "Expected \"{$wrappedType->name}!\", found null." ];
|
||||
}
|
||||
return ['Expected non-null value, found null.'];
|
||||
}
|
||||
return static::isValidLiteralValue($wrappedType, $valueAST);
|
||||
}
|
||||
|
||||
// Unwrap non-null.
|
||||
if ($type instanceof NonNull) {
|
||||
return static::isValidLiteralValue($valueAST, $type->getWrappedType());
|
||||
if (!$valueAST) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// This function only tests literals, and assumes variables will provide
|
||||
// values of the correct type.
|
||||
if ($valueAST instanceof Variable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$valueAST instanceof Value) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// Lists accept a non-list value as a list of one.
|
||||
if ($type instanceof ListOfType) {
|
||||
$itemType = $type->getWrappedType();
|
||||
if ($valueAST instanceof ListValue) {
|
||||
foreach($valueAST->values as $itemAST) {
|
||||
if (!static::isValidLiteralValue($itemAST, $itemType)) {
|
||||
return false;
|
||||
$errors = [];
|
||||
foreach($valueAST->values as $index => $itemAST) {
|
||||
$tmp = static::isValidLiteralValue($itemType, $itemAST);
|
||||
|
||||
if ($tmp) {
|
||||
$errors = array_merge($errors, Utils::map($tmp, function($error) use ($index) {
|
||||
return "In element #$index: $error";
|
||||
}));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return $errors;
|
||||
} else {
|
||||
return static::isValidLiteralValue($valueAST, $itemType);
|
||||
return static::isValidLiteralValue($itemType, $valueAST);
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar/Enum input checks to ensure the type can serialize the value to
|
||||
// a non-null value.
|
||||
if ($type instanceof ScalarType || $type instanceof EnumType) {
|
||||
return $type->parseLiteral($valueAST) !== null;
|
||||
}
|
||||
|
||||
// Input objects check each defined field, ensuring it is of the correct
|
||||
// type and provided if non-nullable.
|
||||
// Input objects check each defined field and look for undefined fields.
|
||||
if ($type instanceof InputObjectType) {
|
||||
$fields = $type->getFields();
|
||||
if ($valueAST->kind !== Node::OBJECT) {
|
||||
return false;
|
||||
return [ "Expected \"{$type->name}\", found not an object." ];
|
||||
}
|
||||
|
||||
$fields = $type->getFields();
|
||||
$errors = [];
|
||||
|
||||
// Ensure every provided field is defined.
|
||||
$fieldASTs = $valueAST->fields;
|
||||
$fieldASTMap = Utils::keyMap($fieldASTs, function($field) {return $field->name->value;});
|
||||
|
||||
foreach ($fields as $fieldKey => $field) {
|
||||
$fieldName = $field->name ?: $fieldKey;
|
||||
if (!isset($fieldASTMap[$fieldName]) && $field->getType() instanceof NonNull) {
|
||||
// Required fields missing
|
||||
return false;
|
||||
foreach ($fieldASTs as $providedFieldAST) {
|
||||
if (empty($fields[$providedFieldAST->name->value])) {
|
||||
$errors[] = "In field \"{$providedFieldAST->name->value}\": Unknown field.";
|
||||
}
|
||||
}
|
||||
foreach ($fieldASTs as $fieldAST) {
|
||||
if (empty($fields[$fieldAST->name->value]) || !static::isValidLiteralValue($fieldAST->value, $fields[$fieldAST->name->value]->getType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Any other kind of type is not an input type, and a literal cannot be used.
|
||||
return false;
|
||||
// Ensure every defined field is valid.
|
||||
$fieldASTMap = Utils::keyMap($fieldASTs, function($fieldAST) {return $fieldAST->name->value;});
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
$result = static::isValidLiteralValue(
|
||||
$field->getType(),
|
||||
isset($fieldASTMap[$fieldName]) ? $fieldASTMap[$fieldName]->value : null
|
||||
);
|
||||
if ($result) {
|
||||
$errors = array_merge($errors, Utils::map($result, function($error) use ($fieldName) {
|
||||
return "In field \"$fieldName\": $error";
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
Utils::invariant(
|
||||
$type instanceof ScalarType || $type instanceof EnumType,
|
||||
'Must be input type'
|
||||
);
|
||||
|
||||
// Scalar/Enum input checks to ensure the type can parse the value to
|
||||
// a non-null value.
|
||||
$parseResult = $type->parseLiteral($valueAST);
|
||||
|
||||
if (null === $parseResult) {
|
||||
$printed = Printer::doPrint($valueAST);
|
||||
return [ "Expected type \"{$type->name}\", found $printed." ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,14 +245,23 @@ class DocumentValidator
|
||||
* while maintaining the visitor skip and break API.
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param TypeInfo $typeInfo
|
||||
* @param Document $documentAST
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public static function visitUsingRules(Schema $schema, Document $documentAST, array $rules)
|
||||
public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, Document $documentAST, array $rules)
|
||||
{
|
||||
$typeInfo = new TypeInfo($schema);
|
||||
$context = new ValidationContext($schema, $documentAST, $typeInfo);
|
||||
$visitors = [];
|
||||
foreach ($rules as $rule) {
|
||||
$visitors[] = $rule($context);
|
||||
}
|
||||
Visitor::visit($documentAST, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
|
||||
return $context->getErrors();
|
||||
|
||||
|
||||
|
||||
$errors = [];
|
||||
|
||||
// TODO: convert to class
|
||||
@ -237,7 +286,7 @@ class DocumentValidator
|
||||
if ($node->kind === Node::FRAGMENT_DEFINITION && $key !== null && !empty($instances[$i]['visitSpreadFragments'])) {
|
||||
$result = Visitor::skipNode();
|
||||
} else {
|
||||
$enter = Visitor::getVisitFn($instances[$i], false, $node->kind);
|
||||
$enter = Visitor::getVisitFn($instances[$i], $node->kind, false);
|
||||
if ($enter instanceof \Closure) {
|
||||
// $enter = $enter->bindTo($instances[$i]);
|
||||
$result = call_user_func_array($enter, func_get_args());
|
||||
@ -266,7 +315,7 @@ class DocumentValidator
|
||||
} else if ($result && static::isError($result)) {
|
||||
static::append($errors, $result);
|
||||
for ($j = $i - 1; $j >= 0; $j--) {
|
||||
$leaveFn = Visitor::getVisitFn($instances[$j], true, $node->kind);
|
||||
$leaveFn = Visitor::getVisitFn($instances[$j], $node->kind, true);
|
||||
if ($leaveFn) {
|
||||
// $leaveFn = $leaveFn->bindTo($instances[$j])
|
||||
$result = call_user_func_array($leaveFn, func_get_args());
|
||||
@ -316,7 +365,7 @@ class DocumentValidator
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$leaveFn = Visitor::getVisitFn($instances[$i], true, $node->kind);
|
||||
$leaveFn = Visitor::getVisitFn($instances[$i], $node->kind, true);
|
||||
|
||||
if ($leaveFn) {
|
||||
// $leaveFn = $leaveFn.bindTo($instances[$i]);
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Validator;
|
||||
|
||||
use GraphQL\Utils;
|
||||
|
||||
class Messages
|
||||
{
|
||||
static function missingArgMessage($fieldName, $argName, $typeName)
|
||||
@ -13,23 +15,6 @@ class Messages
|
||||
return "Argument $argName expected type $typeName but got: $value.";
|
||||
}
|
||||
|
||||
static function defaultForNonNullArgMessage($varName, $typeName, $guessTypeName)
|
||||
{
|
||||
return "Variable \$$varName of type $typeName " .
|
||||
"is required and will never use the default value. " .
|
||||
"Perhaps you meant to use type $guessTypeName.";
|
||||
}
|
||||
|
||||
static function badValueForDefaultArgMessage($varName, $typeName, $value)
|
||||
{
|
||||
return "Variable \$$varName of type $typeName has invalid default value: $value.";
|
||||
}
|
||||
|
||||
static function undefinedFieldMessage($field, $type)
|
||||
{
|
||||
return 'Cannot query field ' . $field . ' on ' . $type;
|
||||
}
|
||||
|
||||
static function fragmentOnNonCompositeErrorMessage($fragName, $typeName)
|
||||
{
|
||||
return "Fragment $fragName cannot condition on non composite type \"$typeName\".";
|
||||
|
@ -16,9 +16,10 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class ArgumentsOfCorrectType
|
||||
{
|
||||
static function badValueMessage($argName, $type, $value)
|
||||
static function badValueMessage($argName, $type, $value, $verboseErrors = [])
|
||||
{
|
||||
return "Argument \"$argName\" expected type \"$type\" but got: $value.";
|
||||
$message = $verboseErrors ? ("\n" . implode("\n", $verboseErrors)) : '';
|
||||
return "Argument \"$argName\" has invalid value $value.$message";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
@ -26,13 +27,18 @@ class ArgumentsOfCorrectType
|
||||
return [
|
||||
Node::ARGUMENT => function(Argument $argAST) use ($context) {
|
||||
$argDef = $context->getArgument();
|
||||
if ($argDef && !DocumentValidator::isValidLiteralValue($argAST->value, $argDef->getType())) {
|
||||
return new Error(
|
||||
self::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value)),
|
||||
if ($argDef) {
|
||||
$errors = DocumentValidator::isValidLiteralValue($argDef->getType(), $argAST->value);
|
||||
|
||||
if (!empty($errors)) {
|
||||
$context->reportError(new Error(
|
||||
self::badValueMessage($argAST->name->value, $argDef->getType(), Printer::doPrint($argAST->value), $errors),
|
||||
[$argAST->value]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,19 @@ use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class DefaultValuesOfCorrectType
|
||||
{
|
||||
static function badValueForDefaultArgMessage($varName, $type, $value, $verboseErrors = null)
|
||||
{
|
||||
$message = $verboseErrors ? ("\n" . implode("\n", $verboseErrors)) : '';
|
||||
return "Variable \$$varName has invalid default value: $value.$message";
|
||||
}
|
||||
|
||||
static function defaultForNonNullArgMessage($varName, $type, $guessType)
|
||||
{
|
||||
return "Variable \$$varName of type $type " .
|
||||
"is required and will never use the default value. " .
|
||||
"Perhaps you meant to use type $guessType.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
@ -22,16 +35,19 @@ class DefaultValuesOfCorrectType
|
||||
$type = $context->getInputType();
|
||||
|
||||
if ($type instanceof NonNull && $defaultValue) {
|
||||
return new Error(
|
||||
Messages::defaultForNonNullArgMessage($name, $type, $type->getWrappedType()),
|
||||
$context->reportError(new Error(
|
||||
static::defaultForNonNullArgMessage($name, $type, $type->getWrappedType()),
|
||||
[$defaultValue]
|
||||
);
|
||||
));
|
||||
}
|
||||
if ($type && $defaultValue && !DocumentValidator::isValidLiteralValue($defaultValue, $type)) {
|
||||
return new Error(
|
||||
Messages::badValueForDefaultArgMessage($name, $type, Printer::doPrint($defaultValue)),
|
||||
if ($type && $defaultValue) {
|
||||
$errors = DocumentValidator::isValidLiteralValue($type, $defaultValue);
|
||||
if (!empty($errors)) {
|
||||
$context->reportError(new Error(
|
||||
static::badValueForDefaultArgMessage($name, $type, Printer::doPrint($defaultValue), $errors),
|
||||
[$defaultValue]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -5,11 +5,34 @@ namespace GraphQL\Validator\Rules;
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Field;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\AbstractType;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class FieldsOnCorrectType
|
||||
{
|
||||
static function undefinedFieldMessage($field, $type, array $suggestedTypes = [])
|
||||
{
|
||||
$message = 'Cannot query field "' . $field . '" on type "' . $type.'".';
|
||||
|
||||
$maxLength = 5;
|
||||
$count = count($suggestedTypes);
|
||||
if ($count > 0) {
|
||||
$suggestions = array_slice($suggestedTypes, 0, $maxLength);
|
||||
$suggestions = Utils::map($suggestions, function($t) { return "\"$t\""; });
|
||||
$suggestions = implode(', ', $suggestions);
|
||||
|
||||
if ($count > $maxLength) {
|
||||
$suggestions .= ', and ' . ($count - $maxLength) . ' other types';
|
||||
}
|
||||
$message .= " However, this field exists on $suggestions.";
|
||||
$message .= ' Perhaps you meant to use an inline fragment?';
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
return [
|
||||
@ -18,13 +41,71 @@ class FieldsOnCorrectType
|
||||
if ($type) {
|
||||
$fieldDef = $context->getFieldDef();
|
||||
if (!$fieldDef) {
|
||||
return new Error(
|
||||
Messages::undefinedFieldMessage($node->name->value, $type->name),
|
||||
[$node]
|
||||
// This isn't valid. Let's find suggestions, if any.
|
||||
$suggestedTypes = [];
|
||||
if ($type instanceof AbstractType) {
|
||||
$schema = $context->getSchema();
|
||||
$suggestedTypes = self::getSiblingInterfacesIncludingField(
|
||||
$schema,
|
||||
$type,
|
||||
$node->name->value
|
||||
);
|
||||
$suggestedTypes = array_merge($suggestedTypes,
|
||||
self::getImplementationsIncludingField($schema, $type, $node->name->value)
|
||||
);
|
||||
}
|
||||
$context->reportError(new Error(
|
||||
static::undefinedFieldMessage($node->name->value, $type->name, $suggestedTypes),
|
||||
[$node]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return implementations of `type` that include `fieldName` as a valid field.
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param AbstractType $type
|
||||
* @param $fieldName
|
||||
* @return array
|
||||
*/
|
||||
static function getImplementationsIncludingField(Schema $schema, AbstractType $type, $fieldName)
|
||||
{
|
||||
$types = $schema->getPossibleTypes($type);
|
||||
$types = Utils::filter($types, function($t) use ($fieldName) {return isset($t->getFields()[$fieldName]);});
|
||||
$types = Utils::map($types, function($t) {return $t->name;});
|
||||
sort($types);
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through all of the implementations of type, and find other interaces
|
||||
* that they implement. If those interfaces include `field` as a valid field,
|
||||
* return them, sorted by how often the implementations include the other
|
||||
* interface.
|
||||
*/
|
||||
static function getSiblingInterfacesIncludingField(Schema $schema, AbstractType $type, $fieldName)
|
||||
{
|
||||
$types = $schema->getPossibleTypes($type);
|
||||
$suggestedInterfaces = array_reduce($types, function ($acc, $t) use ($fieldName) {
|
||||
foreach ($t->getInterfaces() as $i) {
|
||||
if (empty($i->getFields()[$fieldName])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($acc[$i->name])) {
|
||||
$acc[$i->name] = 0;
|
||||
}
|
||||
$acc[$i->name] += 1;
|
||||
}
|
||||
return $acc;
|
||||
}, []);
|
||||
$suggestedInterfaceNames = array_keys($suggestedInterfaces);
|
||||
usort($suggestedInterfaceNames, function($a, $b) use ($suggestedInterfaces) {
|
||||
return $suggestedInterfaces[$b] - $suggestedInterfaces[$a];
|
||||
});
|
||||
return $suggestedInterfaceNames;
|
||||
}
|
||||
}
|
||||
|
@ -30,21 +30,21 @@ class FragmentsOnCompositeTypes
|
||||
Node::INLINE_FRAGMENT => function(InlineFragment $node) use ($context) {
|
||||
$type = $context->getType();
|
||||
|
||||
if ($type && !Type::isCompositeType($type)) {
|
||||
return new Error(
|
||||
self::inlineFragmentOnNonCompositeErrorMessage($type),
|
||||
if ($node->typeCondition && $type && !Type::isCompositeType($type)) {
|
||||
$context->reportError(new Error(
|
||||
static::inlineFragmentOnNonCompositeErrorMessage($type),
|
||||
[$node->typeCondition]
|
||||
);
|
||||
));
|
||||
}
|
||||
},
|
||||
Node::FRAGMENT_DEFINITION => function(FragmentDefinition $node) use ($context) {
|
||||
$type = $context->getType();
|
||||
|
||||
if ($type && !Type::isCompositeType($type)) {
|
||||
return new Error(
|
||||
self::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)),
|
||||
$context->reportError(new Error(
|
||||
static::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)),
|
||||
[$node->typeCondition]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -40,10 +40,10 @@ class KnownArgumentNames
|
||||
if (!$fieldArgDef) {
|
||||
$parentType = $context->getParentType();
|
||||
Utils::invariant($parentType);
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::unknownArgMessage($node->name->value, $fieldDef->name, $parentType->name),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if ($argumentOf->kind === Node::DIRECTIVE) {
|
||||
@ -57,10 +57,10 @@ class KnownArgumentNames
|
||||
}
|
||||
}
|
||||
if (!$directiveArgDef) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::unknownDirectiveArgMessage($node->name->value, $directive->name),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
use GraphQL\Type\Definition\Directive as DirectiveDef;
|
||||
|
||||
class KnownDirectives
|
||||
{
|
||||
@ -20,9 +21,9 @@ class KnownDirectives
|
||||
return "Unknown directive \"$directiveName\".";
|
||||
}
|
||||
|
||||
static function misplacedDirectiveMessage($directiveName, $placement)
|
||||
static function misplacedDirectiveMessage($directiveName, $location)
|
||||
{
|
||||
return "Directive \"$directiveName\" may not be used on \"$placement\".";
|
||||
return "Directive \"$directiveName\" may not be used on \"$location\".";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
@ -38,39 +39,44 @@ class KnownDirectives
|
||||
}
|
||||
|
||||
if (!$directiveDef) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::unknownDirectiveMessage($node->name->value),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
return ;
|
||||
}
|
||||
$appliedTo = $ancestors[count($ancestors) - 1];
|
||||
$candidateLocation = $this->getLocationForAppliedNode($appliedTo);
|
||||
|
||||
if ($appliedTo instanceof OperationDefinition && !$directiveDef->onOperation) {
|
||||
return new Error(
|
||||
self::misplacedDirectiveMessage($node->name->value, 'operation'),
|
||||
if (!$candidateLocation) {
|
||||
$context->reportError(new Error(
|
||||
self::misplacedDirectiveMessage($node->name->value, $node->type),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
if ($appliedTo instanceof Field && !$directiveDef->onField) {
|
||||
return new Error(
|
||||
self::misplacedDirectiveMessage($node->name->value, 'field'),
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
|
||||
$fragmentKind = (
|
||||
$appliedTo instanceof FragmentSpread ||
|
||||
$appliedTo instanceof InlineFragment ||
|
||||
$appliedTo instanceof FragmentDefinition
|
||||
);
|
||||
|
||||
if ($fragmentKind && !$directiveDef->onFragment) {
|
||||
return new Error(
|
||||
self::misplacedDirectiveMessage($node->name->value, 'fragment'),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
} else if (!in_array($candidateLocation, $directiveDef->locations)) {
|
||||
$context->reportError(new Error(
|
||||
self::misplacedDirectiveMessage($node->name->value, $candidateLocation),
|
||||
[ $node ]
|
||||
));
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private function getLocationForAppliedNode(Node $appliedTo)
|
||||
{
|
||||
switch ($appliedTo->kind) {
|
||||
case Node::OPERATION_DEFINITION:
|
||||
switch ($appliedTo->operation) {
|
||||
case 'query': return DirectiveDef::$directiveLocations['QUERY'];
|
||||
case 'mutation': return DirectiveDef::$directiveLocations['MUTATION'];
|
||||
case 'subscription': return DirectiveDef::$directiveLocations['SUBSCRIPTION'];
|
||||
}
|
||||
break;
|
||||
case Node::FIELD: return DirectiveDef::$directiveLocations['FIELD'];
|
||||
case Node::FRAGMENT_SPREAD: return DirectiveDef::$directiveLocations['FRAGMENT_SPREAD'];
|
||||
case Node::INLINE_FRAGMENT: return DirectiveDef::$directiveLocations['INLINE_FRAGMENT'];
|
||||
case Node::FRAGMENT_DEFINITION: return DirectiveDef::$directiveLocations['FRAGMENT_DEFINITION'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ class KnownFragmentNames
|
||||
$fragmentName = $node->name->value;
|
||||
$fragment = $context->getFragment($fragmentName);
|
||||
if (!$fragment) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::unknownFragmentMessage($fragmentName),
|
||||
[$node->name]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -6,6 +6,7 @@ use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
@ -18,14 +19,19 @@ class KnownTypeNames
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$skip = function() {return Visitor::skipNode();};
|
||||
|
||||
return [
|
||||
Node::OBJECT_TYPE_DEFINITION => $skip,
|
||||
Node::INTERFACE_TYPE_DEFINITION => $skip,
|
||||
Node::UNION_TYPE_DEFINITION => $skip,
|
||||
Node::INPUT_OBJECT_TYPE_DEFINITION => $skip,
|
||||
|
||||
Node::NAMED_TYPE => function(NamedType $node, $key) use ($context) {
|
||||
if ($key === 'type' || $key === 'typeCondition') {
|
||||
$typeName = $node->name->value;
|
||||
$type = $context->getSchema()->getType($typeName);
|
||||
if (!$type) {
|
||||
return new Error(self::unknownTypeMessage($typeName), [$node]);
|
||||
}
|
||||
$context->reportError(new Error(self::unknownTypeMessage($typeName), [$node]));
|
||||
}
|
||||
}
|
||||
];
|
||||
|
46
src/Validator/Rules/LoneAnonymousOperation.php
Normal file
46
src/Validator/Rules/LoneAnonymousOperation.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace GraphQL\Validator\Rules;
|
||||
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Language\AST\Document;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
/**
|
||||
* Lone 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.
|
||||
*/
|
||||
class LoneAnonymousOperation extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
static function anonOperationNotAloneMessage()
|
||||
{
|
||||
return 'This anonymous operation must be the only defined operation.';
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$operationCount = 0;
|
||||
return [
|
||||
Node::DOCUMENT => function(Document $node) use (&$operationCount) {
|
||||
$tmp = Utils::filter(
|
||||
$node->definitions,
|
||||
function ($definition) {
|
||||
return $definition->kind === Node::OPERATION_DEFINITION;
|
||||
}
|
||||
);
|
||||
$operationCount = count($tmp);
|
||||
},
|
||||
Node::OPERATION_DEFINITION => function(OperationDefinition $node) use (&$operationCount, $context) {
|
||||
if (!$node->name && $operationCount > 1) {
|
||||
$context->reportError(
|
||||
new Error(self::anonOperationNotAloneMessage(), [$node])
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ use GraphQL\Language\AST\FragmentDefinition;
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Utils;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
class NoFragmentCycles
|
||||
@ -24,83 +25,86 @@ class NoFragmentCycles
|
||||
return "Cannot spread fragment \"$fragName\" within itself$via.";
|
||||
}
|
||||
|
||||
public $visitedFrags;
|
||||
|
||||
public $spreadPath;
|
||||
|
||||
public $spreadPathIndexByName;
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
// Gather all the fragment spreads ASTs for each fragment definition.
|
||||
// Importantly this does not include inline fragments.
|
||||
$definitions = $context->getDocument()->definitions;
|
||||
$spreadsInFragment = [];
|
||||
foreach ($definitions as $node) {
|
||||
if ($node instanceof FragmentDefinition) {
|
||||
$spreadsInFragment[$node->name->value] = $this->gatherSpreads($node);
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks spreads known to lead to cycles to ensure that cycles are not
|
||||
// redundantly reported.
|
||||
$knownToLeadToCycle = new \SplObjectStorage();
|
||||
|
||||
return [
|
||||
Node::FRAGMENT_DEFINITION => function(FragmentDefinition $node) use ($spreadsInFragment, $knownToLeadToCycle) {
|
||||
$errors = [];
|
||||
$initialName = $node->name->value;
|
||||
// Tracks already visited fragments to maintain O(N) and to ensure that cycles
|
||||
// are not redundantly reported.
|
||||
$this->visitedFrags = [];
|
||||
|
||||
// Array of AST nodes used to produce meaningful errors
|
||||
$spreadPath = [];
|
||||
$this->spreadPath = [];
|
||||
|
||||
$this->detectCycleRecursive($initialName, $spreadsInFragment, $knownToLeadToCycle, $initialName, $spreadPath, $errors);
|
||||
// Position in the spread path
|
||||
$this->spreadPathIndexByName = [];
|
||||
|
||||
if (!empty($errors)) {
|
||||
return $errors;
|
||||
return [
|
||||
Node::OPERATION_DEFINITION => function () {
|
||||
return Visitor::skipNode();
|
||||
},
|
||||
Node::FRAGMENT_DEFINITION => function (FragmentDefinition $node) use ($context) {
|
||||
if (!isset($this->visitedFrags[$node->name->value])) {
|
||||
$this->detectCycleRecursive($node, $context);
|
||||
}
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private function detectCycleRecursive($fragmentName, array $spreadsInFragment, \SplObjectStorage $knownToLeadToCycle, $initialName, array &$spreadPath, &$errors)
|
||||
private function detectCycleRecursive(FragmentDefinition $fragment, ValidationContext $context)
|
||||
{
|
||||
$spreadNodes = $spreadsInFragment[$fragmentName];
|
||||
$fragmentName = $fragment->name->value;
|
||||
$this->visitedFrags[$fragmentName] = true;
|
||||
|
||||
for ($i = 0; $i < count($spreadNodes); ++$i) {
|
||||
$spreadNodes = $context->getFragmentSpreads($fragment);
|
||||
|
||||
if (empty($spreadNodes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->spreadPathIndexByName[$fragmentName] = count($this->spreadPath);
|
||||
|
||||
for ($i = 0; $i < count($spreadNodes); $i++) {
|
||||
$spreadNode = $spreadNodes[$i];
|
||||
if (isset($knownToLeadToCycle[$spreadNode])) {
|
||||
continue ;
|
||||
$spreadName = $spreadNode->name->value;
|
||||
$cycleIndex = isset($this->spreadPathIndexByName[$spreadName]) ? $this->spreadPathIndexByName[$spreadName] : null;
|
||||
|
||||
if ($cycleIndex === null) {
|
||||
$this->spreadPath[] = $spreadNode;
|
||||
if (empty($this->visitedFrags[$spreadName])) {
|
||||
$spreadFragment = $context->getFragment($spreadName);
|
||||
if ($spreadFragment) {
|
||||
$this->detectCycleRecursive($spreadFragment, $context);
|
||||
}
|
||||
if ($spreadNode->name->value === $initialName) {
|
||||
$cyclePath = array_merge($spreadPath, [$spreadNode]);
|
||||
foreach ($cyclePath as $spread) {
|
||||
$knownToLeadToCycle[$spread] = true;
|
||||
}
|
||||
$errors[] = new Error(
|
||||
self::cycleErrorMessage($initialName, array_map(function ($s) {
|
||||
array_pop($this->spreadPath);
|
||||
} else {
|
||||
$cyclePath = array_slice($this->spreadPath, $cycleIndex);
|
||||
$nodes = $cyclePath;
|
||||
|
||||
if (is_array($spreadNode)) {
|
||||
$nodes = array_merge($nodes, $spreadNode);
|
||||
} else {
|
||||
$nodes[] = $spreadNode;
|
||||
}
|
||||
|
||||
$context->reportError(new Error(
|
||||
self::cycleErrorMessage(
|
||||
$spreadName,
|
||||
Utils::map($cyclePath, function ($s) {
|
||||
return $s->name->value;
|
||||
}, $spreadPath)),
|
||||
$cyclePath
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($spreadPath as $spread) {
|
||||
if ($spread === $spreadNode) {
|
||||
continue 2;
|
||||
})
|
||||
),
|
||||
$nodes
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$spreadPath[] = $spreadNode;
|
||||
$this->detectCycleRecursive($spreadNode->name->value, $spreadsInFragment, $knownToLeadToCycle, $initialName, $spreadPath, $errors);
|
||||
array_pop($spreadPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function gatherSpreads($node)
|
||||
{
|
||||
$spreadNodes = [];
|
||||
Visitor::visit($node, [
|
||||
Node::FRAGMENT_SPREAD => function(FragmentSpread $spread) use (&$spreadNodes) {
|
||||
$spreadNodes[] = $spread;
|
||||
}
|
||||
]);
|
||||
return $spreadNodes;
|
||||
$this->spreadPathIndexByName[$fragmentName] = null;
|
||||
}
|
||||
}
|
||||
|
@ -23,62 +23,43 @@ use GraphQL\Validator\ValidationContext;
|
||||
*/
|
||||
class NoUndefinedVariables
|
||||
{
|
||||
static function undefinedVarMessage($varName)
|
||||
static function undefinedVarMessage($varName, $opName = null)
|
||||
{
|
||||
return "Variable \"$$varName\" is not defined.";
|
||||
}
|
||||
|
||||
static function undefinedVarByOpMessage($varName, $opName)
|
||||
{
|
||||
return "Variable \"$$varName\" is not defined by operation \"$opName\".";
|
||||
return $opName
|
||||
? "Variable \"$$varName\" is not defined by operation \"$opName\"."
|
||||
: "Variable \"$$varName\" is not defined.";
|
||||
}
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$operation = null;
|
||||
$visitedFragmentNames = [];
|
||||
$definedVariableNames = [];
|
||||
$variableNameDefined = [];
|
||||
|
||||
return [
|
||||
// Visit FragmentDefinition after visiting FragmentSpread
|
||||
'visitSpreadFragments' => true,
|
||||
Node::OPERATION_DEFINITION => [
|
||||
'enter' => function() use (&$variableNameDefined) {
|
||||
$variableNameDefined = [];
|
||||
},
|
||||
'leave' => function(OperationDefinition $operation) use (&$variableNameDefined, $context) {
|
||||
$usages = $context->getRecursiveVariableUsages($operation);
|
||||
|
||||
Node::OPERATION_DEFINITION => function(OperationDefinition $node, $key, $parent, $path, $ancestors) use (&$operation, &$visitedFragmentNames, &$definedVariableNames) {
|
||||
$operation = $node;
|
||||
$visitedFragmentNames = [];
|
||||
$definedVariableNames = [];
|
||||
},
|
||||
Node::VARIABLE_DEFINITION => function(VariableDefinition $def) use (&$definedVariableNames) {
|
||||
$definedVariableNames[$def->variable->name->value] = true;
|
||||
},
|
||||
Node::VARIABLE => function(Variable $variable, $key, $parent, $path, $ancestors) use (&$definedVariableNames, &$visitedFragmentNames, &$operation) {
|
||||
$varName = $variable->name->value;
|
||||
if (empty($definedVariableNames[$varName])) {
|
||||
$withinFragment = false;
|
||||
foreach ($ancestors as $ancestor) {
|
||||
if ($ancestor instanceof FragmentDefinition) {
|
||||
$withinFragment = true;
|
||||
break;
|
||||
foreach ($usages as $usage) {
|
||||
$node = $usage['node'];
|
||||
$varName = $node->name->value;
|
||||
|
||||
if (empty($variableNameDefined[$varName])) {
|
||||
$context->reportError(new Error(
|
||||
self::undefinedVarMessage(
|
||||
$varName,
|
||||
$operation->name ? $operation->name->value : null
|
||||
),
|
||||
[ $node, $operation ]
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($withinFragment && $operation && $operation->name) {
|
||||
return new Error(
|
||||
self::undefinedVarByOpMessage($varName, $operation->name->value),
|
||||
[$variable, $operation]
|
||||
);
|
||||
}
|
||||
return new Error(
|
||||
self::undefinedVarMessage($varName),
|
||||
[$variable]
|
||||
);
|
||||
}
|
||||
},
|
||||
Node::FRAGMENT_SPREAD => function(FragmentSpread $spreadAST) use (&$visitedFragmentNames) {
|
||||
// Only visit fragments of a particular name once per operation
|
||||
if (!empty($visitedFragmentNames[$spreadAST->name->value])) {
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
$visitedFragmentNames[$spreadAST->name->value] = true;
|
||||
],
|
||||
Node::VARIABLE_DEFINITION => function(VariableDefinition $def) use (&$variableNameDefined) {
|
||||
$variableNameDefined[$def->variable->name->value] = true;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use GraphQL\Error;
|
||||
use GraphQL\Language\AST\FragmentDefinition;
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Visitor;
|
||||
use GraphQL\Validator\Messages;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
@ -16,63 +17,45 @@ class NoUnusedFragments
|
||||
return "Fragment \"$fragName\" is never used.";
|
||||
}
|
||||
|
||||
public $operationDefs;
|
||||
|
||||
public $fragmentDefs;
|
||||
|
||||
public function __invoke(ValidationContext $context)
|
||||
{
|
||||
$fragmentDefs = [];
|
||||
$spreadsWithinOperation = [];
|
||||
$fragAdjacencies = new \stdClass();
|
||||
$spreadNames = new \stdClass();
|
||||
$this->operationDefs = [];
|
||||
$this->fragmentDefs = [];
|
||||
|
||||
return [
|
||||
Node::OPERATION_DEFINITION => function() use (&$spreadNames, &$spreadsWithinOperation) {
|
||||
$spreadNames = new \stdClass();
|
||||
$spreadsWithinOperation[] = $spreadNames;
|
||||
Node::OPERATION_DEFINITION => function($node) {
|
||||
$this->operationDefs[] = $node;
|
||||
return Visitor::skipNode();
|
||||
},
|
||||
Node::FRAGMENT_DEFINITION => function(FragmentDefinition $def) use (&$fragmentDefs, &$spreadNames, &$fragAdjacencies) {
|
||||
$fragmentDefs[] = $def;
|
||||
$spreadNames = new \stdClass();
|
||||
$fragAdjacencies->{$def->name->value} = $spreadNames;
|
||||
},
|
||||
Node::FRAGMENT_SPREAD => function(FragmentSpread $spread) use (&$spreadNames) {
|
||||
$spreadNames->{$spread->name->value} = true;
|
||||
Node::FRAGMENT_DEFINITION => function(FragmentDefinition $def) {
|
||||
$this->fragmentDefs[] = $def;
|
||||
return Visitor::skipNode();
|
||||
},
|
||||
Node::DOCUMENT => [
|
||||
'leave' => function() use (&$fragAdjacencies, &$spreadsWithinOperation, &$fragmentDefs) {
|
||||
'leave' => function() use ($context) {
|
||||
$fragmentNameUsed = [];
|
||||
|
||||
foreach ($spreadsWithinOperation as $spreads) {
|
||||
$this->reduceSpreadFragments($spreads, $fragmentNameUsed, $fragAdjacencies);
|
||||
foreach ($this->operationDefs as $operation) {
|
||||
foreach ($context->getRecursivelyReferencedFragments($operation) as $fragment) {
|
||||
$fragmentNameUsed[$fragment->name->value] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
foreach ($fragmentDefs as $def) {
|
||||
if (empty($fragmentNameUsed[$def->name->value])) {
|
||||
$errors[] = new Error(
|
||||
self::unusedFragMessage($def->name->value),
|
||||
[$def]
|
||||
);
|
||||
foreach ($this->fragmentDefs as $fragmentDef) {
|
||||
$fragName = $fragmentDef->name->value;
|
||||
if (empty($fragmentNameUsed[$fragName])) {
|
||||
$context->reportError(new Error(
|
||||
self::unusedFragMessage($fragName),
|
||||
[ $fragmentDef ]
|
||||
));
|
||||
}
|
||||
}
|
||||
return !empty($errors) ? $errors : null;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function reduceSpreadFragments($spreads, &$fragmentNameUsed, &$fragAdjacencies)
|
||||
{
|
||||
foreach ($spreads as $fragName => $fragment) {
|
||||
if (empty($fragmentNameUsed[$fragName])) {
|
||||
$fragmentNameUsed[$fragName] = true;
|
||||
|
||||
if (isset($fragAdjacencies->{$fragName})) {
|
||||
$this->reduceSpreadFragments(
|
||||
$fragAdjacencies->{$fragName},
|
||||
$fragmentNameUsed,
|
||||
$fragAdjacencies
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,18 +54,15 @@ class OverlappingFieldsCanBeMerged
|
||||
|
||||
$conflicts = $this->findConflicts($fieldMap, $context, $comparedSet);
|
||||
|
||||
if (!empty($conflicts)) {
|
||||
return array_map(function ($conflict) {
|
||||
foreach ($conflicts as $conflict) {
|
||||
$responseName = $conflict[0][0];
|
||||
$reason = $conflict[0][1];
|
||||
$fields = $conflict[1];
|
||||
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::fieldsConflictMessage($responseName, $reason),
|
||||
$fields
|
||||
);
|
||||
}, $conflicts);
|
||||
|
||||
));
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -32,10 +32,10 @@ class PossibleFragmentSpreads
|
||||
$fragType = Type::getNamedType($context->getType());
|
||||
$parentType = $context->getParentType();
|
||||
if ($fragType && $parentType && !$this->doTypesOverlap($fragType, $parentType)) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
}
|
||||
},
|
||||
Node::FRAGMENT_SPREAD => function(FragmentSpread $node) use ($context) {
|
||||
@ -44,10 +44,10 @@ class PossibleFragmentSpreads
|
||||
$parentType = $context->getParentType();
|
||||
|
||||
if ($fragType && $parentType && !$this->doTypesOverlap($fragType, $parentType)) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -33,7 +33,6 @@ class ProvidedNonNullArguments
|
||||
if (!$fieldDef) {
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
$errors = [];
|
||||
$argASTs = $fieldAST->arguments ?: [];
|
||||
|
||||
$argASTMap = [];
|
||||
@ -43,16 +42,12 @@ class ProvidedNonNullArguments
|
||||
foreach ($fieldDef->args as $argDef) {
|
||||
$argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
|
||||
if (!$argAST && $argDef->getType() instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
$context->reportError(new Error(
|
||||
self::missingFieldArgMessage($fieldAST->name->value, $argDef->name, $argDef->getType()),
|
||||
[$fieldAST]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
],
|
||||
Node::DIRECTIVE => [
|
||||
@ -61,7 +56,6 @@ class ProvidedNonNullArguments
|
||||
if (!$directiveDef) {
|
||||
return Visitor::skipNode();
|
||||
}
|
||||
$errors = [];
|
||||
$argASTs = $directiveAST->arguments ?: [];
|
||||
$argASTMap = [];
|
||||
foreach ($argASTs as $argAST) {
|
||||
@ -71,15 +65,12 @@ class ProvidedNonNullArguments
|
||||
foreach ($directiveDef->args as $argDef) {
|
||||
$argAST = isset($argASTMap[$argDef->name]) ? $argASTMap[$argDef->name] : null;
|
||||
if (!$argAST && $argDef->getType() instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
$context->reportError(new Error(
|
||||
self::missingDirectiveArgMessage($directiveAST->name->value, $argDef->name, $argDef->getType()),
|
||||
[$directiveAST]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
@ -29,16 +29,16 @@ class ScalarLeafs
|
||||
if ($type) {
|
||||
if (Type::isLeafType($type)) {
|
||||
if ($node->selectionSet) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::noSubselectionAllowedMessage($node->name->value, $type),
|
||||
[$node->selectionSet]
|
||||
);
|
||||
));
|
||||
}
|
||||
} else if (!$node->selectionSet) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::requiredSubselectionMessage($node->name->value, $type),
|
||||
[$node]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,10 +27,10 @@ class VariablesAreInputTypes
|
||||
// If the variable type is not an input type, return an error.
|
||||
if ($type && !Type::isInputType($type)) {
|
||||
$variableName = $node->variable->name->value;
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
|
||||
[ $node->type ]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -47,10 +47,10 @@ class VariablesInAllowedPosition
|
||||
if ($varType && $inputType &&
|
||||
!$this->varTypeAllowedForType($this->effectiveType($varType, $varDef), $inputType)
|
||||
) {
|
||||
return new Error(
|
||||
$context->reportError(new Error(
|
||||
Messages::badVarPosMessage($varName, $varType, $inputType),
|
||||
[$variableAST]
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -1,5 +1,13 @@
|
||||
<?php
|
||||
namespace GraphQL\Validator;
|
||||
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\HasSelectionSet;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Language\AST\Variable;
|
||||
use GraphQL\Language\Visitor;
|
||||
use \SplObjectStorage;
|
||||
use GraphQL\Error;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Language\AST\Document;
|
||||
use GraphQL\Language\AST\FragmentDefinition;
|
||||
@ -33,16 +41,69 @@ class ValidationContext
|
||||
*/
|
||||
private $_typeInfo;
|
||||
|
||||
/**
|
||||
* @var Error[]
|
||||
*/
|
||||
private $_errors;
|
||||
|
||||
/**
|
||||
* @var array<string, FragmentDefinition>
|
||||
*/
|
||||
private $_fragments;
|
||||
|
||||
/**
|
||||
* @var SplObjectStorage
|
||||
*/
|
||||
private $_fragmentSpreads;
|
||||
|
||||
/**
|
||||
* @var SplObjectStorage
|
||||
*/
|
||||
private $_recursivelyReferencedFragments;
|
||||
|
||||
/**
|
||||
* @var SplObjectStorage
|
||||
*/
|
||||
private $_variableUsages;
|
||||
|
||||
/**
|
||||
* @var SplObjectStorage
|
||||
*/
|
||||
private $_recursiveVariableUsages;
|
||||
|
||||
/**
|
||||
* ValidationContext constructor.
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param Document $ast
|
||||
* @param TypeInfo $typeInfo
|
||||
*/
|
||||
function __construct(Schema $schema, Document $ast, TypeInfo $typeInfo)
|
||||
{
|
||||
$this->_schema = $schema;
|
||||
$this->_ast = $ast;
|
||||
$this->_typeInfo = $typeInfo;
|
||||
$this->_errors = [];
|
||||
$this->_fragmentSpreads = new SplObjectStorage();
|
||||
$this->_recursivelyReferencedFragments = new SplObjectStorage();
|
||||
$this->_variableUsages = new SplObjectStorage();
|
||||
$this->_recursiveVariableUsages = new SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Error $error
|
||||
*/
|
||||
function reportError(Error $error)
|
||||
{
|
||||
$this->_errors[] = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Error[]
|
||||
*/
|
||||
function getErrors()
|
||||
{
|
||||
return $this->_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,6 +141,113 @@ class ValidationContext
|
||||
return isset($fragments[$name]) ? $fragments[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HasSelectionSet $node
|
||||
* @return FragmentSpread[]
|
||||
*/
|
||||
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 === Node::FRAGMENT_SPREAD) {
|
||||
$spreads[] = $selection;
|
||||
} else if ($selection->selectionSet) {
|
||||
$setsToVisit[] = $selection->selectionSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->_fragmentSpreads[$node] = $spreads;
|
||||
}
|
||||
return $spreads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OperationDefinition $operation
|
||||
* @return FragmentDefinition[]
|
||||
*/
|
||||
function getRecursivelyReferencedFragments(OperationDefinition $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' => Variable, '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, [
|
||||
Node::VARIABLE_DEFINITION => function () {
|
||||
return false;
|
||||
},
|
||||
Node::VARIABLE => function (Variable $variable) use (&$newUsages, $typeInfo) {
|
||||
$newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
|
||||
}
|
||||
]));
|
||||
$usages = $newUsages;
|
||||
$this->_variableUsages[$node] = $usages;
|
||||
}
|
||||
return $usages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OperationDefinition $operation
|
||||
* @return array List of ['node' => Variable, 'type' => ?InputObjectType]
|
||||
*/
|
||||
function getRecursiveVariableUsages(OperationDefinition $operation)
|
||||
{
|
||||
$usages = isset($this->_recursiveVariableUsages[$operation]) ? $this->_recursiveVariableUsages[$operation] : null;
|
||||
|
||||
if (!$usages) {
|
||||
$usages = $this->getVariableUsages($operation);
|
||||
$fragments = $this->getRecursivelyReferencedFragments($operation);
|
||||
|
||||
$tmp = [$usages];
|
||||
for ($i = 0; $i < count($fragments); $i++) {
|
||||
$tmp[] = $this->getVariableUsages($fragments[$i]);
|
||||
}
|
||||
$usages = call_user_func_array('array_merge', $tmp);
|
||||
$this->_recursiveVariableUsages[$operation] = $usages;
|
||||
}
|
||||
return $usages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns OutputType
|
||||
*
|
||||
|
@ -7,25 +7,22 @@ use GraphQL\Validator\Rules\ArgumentsOfCorrectType;
|
||||
|
||||
class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
{
|
||||
function missingArg($fieldName, $argName, $typeName, $line, $column)
|
||||
function badValue($argName, $typeName, $value, $line, $column, $errors = null)
|
||||
{
|
||||
return FormattedError::create(
|
||||
Messages::missingArgMessage($fieldName, $argName, $typeName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
$realErrors = !$errors ? ["Expected type \"$typeName\", found $value."] : $errors;
|
||||
|
||||
function badValue($argName, $typeName, $value, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
ArgumentsOfCorrectType::badValueMessage($argName, $typeName, $value),
|
||||
ArgumentsOfCorrectType::badValueMessage($argName, $typeName, $value, $realErrors),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
||||
// Validate: Argument values of correct type
|
||||
// Valid values:
|
||||
// Valid values
|
||||
|
||||
/**
|
||||
* @it Good int value
|
||||
*/
|
||||
public function testGoodIntValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -37,6 +34,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Good boolean value
|
||||
*/
|
||||
public function testGoodBooleanValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -48,6 +48,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Good string value
|
||||
*/
|
||||
public function testGoodStringValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -59,6 +62,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Good float value
|
||||
*/
|
||||
public function testGoodFloatValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -70,6 +76,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Int into Float
|
||||
*/
|
||||
public function testIntIntoFloat()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -81,6 +90,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Int into ID
|
||||
*/
|
||||
public function testIntIntoID()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -92,6 +104,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it String into ID
|
||||
*/
|
||||
public function testStringIntoID()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -103,6 +118,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Good enum value
|
||||
*/
|
||||
public function testGoodEnumValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -115,6 +133,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid String values
|
||||
|
||||
/**
|
||||
* @it Int into String
|
||||
*/
|
||||
public function testIntIntoString()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -128,6 +150,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Float into String
|
||||
*/
|
||||
public function testFloatIntoString()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -141,6 +166,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Boolean into String
|
||||
*/
|
||||
public function testBooleanIntoString()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -154,6 +182,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Unquoted String into String
|
||||
*/
|
||||
public function testUnquotedStringIntoString()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -168,6 +199,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid Int values
|
||||
|
||||
/**
|
||||
* @it String into Int
|
||||
*/
|
||||
public function testStringIntoInt()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -181,6 +216,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Big Int into Int
|
||||
*/
|
||||
public function testBigIntIntoInt()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -194,6 +232,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Unquoted String into Int
|
||||
*/
|
||||
public function testUnquotedStringIntoInt()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -207,6 +248,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Simple Float into Int
|
||||
*/
|
||||
public function testSimpleFloatIntoInt()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -220,6 +264,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Float into Int
|
||||
*/
|
||||
public function testFloatIntoInt()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -234,6 +281,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid Float values
|
||||
|
||||
/**
|
||||
* @it String into Float
|
||||
*/
|
||||
public function testStringIntoFloat()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -247,6 +298,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Boolean into Float
|
||||
*/
|
||||
public function testBooleanIntoFloat()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -260,6 +314,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Unquoted into Float
|
||||
*/
|
||||
public function testUnquotedIntoFloat()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -274,6 +331,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid Boolean value
|
||||
|
||||
/**
|
||||
* @it Int into Boolean
|
||||
*/
|
||||
public function testIntIntoBoolean()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -287,6 +348,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Float into Boolean
|
||||
*/
|
||||
public function testFloatIntoBoolean()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -300,6 +364,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it String into Boolean
|
||||
*/
|
||||
public function testStringIntoBoolean()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -313,6 +380,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Unquoted into Boolean
|
||||
*/
|
||||
public function testUnquotedIntoBoolean()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -327,6 +397,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid ID value
|
||||
|
||||
/**
|
||||
* @it Float into ID
|
||||
*/
|
||||
public function testFloatIntoID()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -340,6 +414,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Boolean into ID
|
||||
*/
|
||||
public function testBooleanIntoID()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -353,6 +430,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Unquoted into ID
|
||||
*/
|
||||
public function testUnquotedIntoID()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -367,6 +447,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid Enum value
|
||||
|
||||
/**
|
||||
* @it Int into Enum
|
||||
*/
|
||||
public function testIntIntoEnum()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -380,6 +464,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Float into Enum
|
||||
*/
|
||||
public function testFloatIntoEnum()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -393,6 +480,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it String into Enum
|
||||
*/
|
||||
public function testStringIntoEnum()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -406,6 +496,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Boolean into Enum
|
||||
*/
|
||||
public function testBooleanIntoEnum()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -419,6 +512,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Unknown Enum Value into Enum
|
||||
*/
|
||||
public function testUnknownEnumValueIntoEnum()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -432,6 +528,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Different case Enum Value into Enum
|
||||
*/
|
||||
public function testDifferentCaseEnumValueIntoEnum()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -446,6 +545,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Valid List value
|
||||
|
||||
/**
|
||||
* @it Good list value
|
||||
*/
|
||||
public function testGoodListValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -457,6 +560,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Empty list value
|
||||
*/
|
||||
public function testEmptyListValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -468,6 +574,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Single value into List
|
||||
*/
|
||||
public function testSingleValueIntoList()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -480,6 +589,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid List value
|
||||
|
||||
/**
|
||||
* @it Incorrect item type
|
||||
*/
|
||||
public function testIncorrectItemtype()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -489,10 +602,15 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('stringListArg', '[String]', '["one", 2]', 4, 47),
|
||||
$this->badValue('stringListArg', '[String]', '["one", 2]', 4, 47, [
|
||||
'In element #1: Expected type "String", found 2.'
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Single value of incorrect type
|
||||
*/
|
||||
public function testSingleValueOfIncorrectType()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
@ -502,11 +620,15 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('stringListArg', '[String]', '1', 4, 47),
|
||||
$this->badValue('stringListArg', 'String', '1', 4, 47),
|
||||
]);
|
||||
}
|
||||
|
||||
// Valid non-nullable value
|
||||
|
||||
/**
|
||||
* @it Arg on optional arg
|
||||
*/
|
||||
public function testArgOnOptionalArg()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -518,6 +640,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it No Arg on optional arg
|
||||
*/
|
||||
public function testNoArgOnOptionalArg()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -529,6 +654,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Multiple args
|
||||
*/
|
||||
public function testMultipleArgs()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -540,6 +668,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Multiple args reverse order
|
||||
*/
|
||||
public function testMultipleArgsReverseOrder()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -551,6 +682,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it No args on multiple optional
|
||||
*/
|
||||
public function testNoArgsOnMultipleOptional()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -562,6 +696,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it One arg on multiple optional
|
||||
*/
|
||||
public function testOneArgOnMultipleOptional()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -573,6 +710,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Second arg on multiple optional
|
||||
*/
|
||||
public function testSecondArgOnMultipleOptional()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -584,6 +724,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Multiple reqs on mixedList
|
||||
*/
|
||||
public function testMultipleReqsOnMixedList()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -595,6 +738,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Multiple reqs and one opt on mixedList
|
||||
*/
|
||||
public function testMultipleReqsAndOneOptOnMixedList()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -606,6 +752,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it All reqs and opts on mixedList
|
||||
*/
|
||||
public function testAllReqsAndOptsOnMixedList()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -618,6 +767,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid non-nullable value
|
||||
|
||||
/**
|
||||
* @it Incorrect value type
|
||||
*/
|
||||
public function testIncorrectValueType()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
@ -627,11 +780,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('req2', 'Int!', '"two"', 4, 32),
|
||||
$this->badValue('req1', 'Int!', '"one"', 4, 45),
|
||||
$this->badValue('req2', 'Int', '"two"', 4, 32),
|
||||
$this->badValue('req1', 'Int', '"one"', 4, 45),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Incorrect value and missing argument
|
||||
*/
|
||||
public function testIncorrectValueAndMissingArgument()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
@ -641,12 +797,16 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('req1', 'Int!', '"one"', 4, 32),
|
||||
$this->badValue('req1', 'Int', '"one"', 4, 32),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// Valid input object value
|
||||
|
||||
/**
|
||||
* @it Optional arg, despite required field in type
|
||||
*/
|
||||
public function testOptionalArgDespiteRequiredFieldInType()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -658,6 +818,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Partial object, only required
|
||||
*/
|
||||
public function testPartialObjectOnlyRequired()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -669,6 +832,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Partial object, required field can be falsey
|
||||
*/
|
||||
public function testPartialObjectRequiredFieldCanBeFalsey()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -680,6 +846,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Partial object, including required
|
||||
*/
|
||||
public function testPartialObjectIncludingRequired()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -691,6 +860,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Full object
|
||||
*/
|
||||
public function testFullObject()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType, '
|
||||
@ -708,6 +880,9 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Full object with fields in different order
|
||||
*/
|
||||
public function testFullObjectWithFieldsInDifferentOrder()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
@ -726,6 +901,10 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
|
||||
// Invalid input object value
|
||||
|
||||
/**
|
||||
* @it Partial object, missing required
|
||||
*/
|
||||
public function testPartialObjectMissingRequired()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
@ -735,10 +914,15 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('complexArg', 'ComplexInput', '{intField: 4}', 4, 41),
|
||||
$this->badValue('complexArg', 'ComplexInput', '{intField: 4}', 4, 41, [
|
||||
'In field "requiredField": Expected "Boolean!", found null.'
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Partial object, invalid field type
|
||||
*/
|
||||
public function testPartialObjectInvalidFieldType()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
@ -756,11 +940,15 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
'ComplexInput',
|
||||
'{stringListField: ["one", 2], requiredField: true}',
|
||||
4,
|
||||
41
|
||||
41,
|
||||
[ 'In field "stringListField": In element #1: Expected type "String", found 2.' ]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Partial object, unknown field arg
|
||||
*/
|
||||
public function testPartialObjectUnknownFieldArg()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
@ -778,8 +966,45 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
'ComplexInput',
|
||||
'{requiredField: true, unknownField: "value"}',
|
||||
4,
|
||||
41
|
||||
41,
|
||||
[ 'In field "unknownField": Unknown field.' ]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
// Directive arguments
|
||||
|
||||
/**
|
||||
* @it with directives of valid types
|
||||
*/
|
||||
public function testWithDirectivesOfValidTypes()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
{
|
||||
dog @include(if: true) {
|
||||
name
|
||||
}
|
||||
human @skip(if: false) {
|
||||
name
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it with directive with incorrect types
|
||||
*/
|
||||
public function testWithDirectiveWithIncorrectTypes()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType, '
|
||||
{
|
||||
dog @include(if: "yes") {
|
||||
name @skip(if: ENUM)
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('if', 'Boolean', '"yes"', 3, 28),
|
||||
$this->badValue('if', 'Boolean', 'ENUM', 4, 28),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
{
|
||||
// Validate: Variable default values of correct type
|
||||
|
||||
/**
|
||||
* @it variables with no default values
|
||||
*/
|
||||
public function testVariablesWithNoDefaultValues()
|
||||
{
|
||||
$this->expectPassesRule(new DefaultValuesOfCorrectType, '
|
||||
@ -19,6 +22,9 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it required variables without default values
|
||||
*/
|
||||
public function testRequiredVariablesWithoutDefaultValues()
|
||||
{
|
||||
$this->expectPassesRule(new DefaultValuesOfCorrectType, '
|
||||
@ -28,6 +34,9 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variables with valid default values
|
||||
*/
|
||||
public function testVariablesWithValidDefaultValues()
|
||||
{
|
||||
$this->expectPassesRule(new DefaultValuesOfCorrectType, '
|
||||
@ -41,6 +50,9 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no required variables with default values
|
||||
*/
|
||||
public function testNoRequiredVariablesWithDefaultValues()
|
||||
{
|
||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
||||
@ -53,6 +65,9 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variables with invalid default values
|
||||
*/
|
||||
public function testVariablesWithInvalidDefaultValues()
|
||||
{
|
||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
||||
@ -64,12 +79,21 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
dog { name }
|
||||
}
|
||||
', [
|
||||
$this->badValue('a', 'Int', '"one"', 3, 19),
|
||||
$this->badValue('b', 'String', '4', 4, 22),
|
||||
$this->badValue('c', 'ComplexInput', '"notverycomplex"', 5, 28)
|
||||
$this->badValue('a', 'Int', '"one"', 3, 19, [
|
||||
'Expected type "Int", found "one".'
|
||||
]),
|
||||
$this->badValue('b', 'String', '4', 4, 22, [
|
||||
'Expected type "String", found 4.'
|
||||
]),
|
||||
$this->badValue('c', 'ComplexInput', '"notverycomplex"', 5, 28, [
|
||||
'Expected "ComplexInput", found not an object.'
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it complex variables missing required field
|
||||
*/
|
||||
public function testComplexVariablesMissingRequiredField()
|
||||
{
|
||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
||||
@ -77,10 +101,15 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
dog { name }
|
||||
}
|
||||
', [
|
||||
$this->badValue('a', 'ComplexInput', '{intField: 3}', 2, 53)
|
||||
$this->badValue('a', 'ComplexInput', '{intField: 3}', 2, 53, [
|
||||
'In field "requiredField": Expected "Boolean!", found null.'
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it list variables with invalid item
|
||||
*/
|
||||
public function testListVariablesWithInvalidItem()
|
||||
{
|
||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
||||
@ -88,22 +117,26 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
dog { name }
|
||||
}
|
||||
', [
|
||||
$this->badValue('a', '[String]', '["one", 2]', 2, 40)
|
||||
$this->badValue('a', '[String]', '["one", 2]', 2, 40, [
|
||||
'In element #1: Expected type "String", found 2.'
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
private function defaultForNonNullArg($varName, $typeName, $guessTypeName, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
Messages::defaultForNonNullArgMessage($varName, $typeName, $guessTypeName),
|
||||
DefaultValuesOfCorrectType::defaultForNonNullArgMessage($varName, $typeName, $guessTypeName),
|
||||
[ new SourceLocation($line, $column) ]
|
||||
);
|
||||
}
|
||||
|
||||
private function badValue($varName, $typeName, $val, $line, $column)
|
||||
private function badValue($varName, $typeName, $val, $line, $column, $errors = null)
|
||||
{
|
||||
$realErrors = !$errors ? ["Expected type \"$typeName\", found $val."] : $errors;
|
||||
|
||||
return FormattedError::create(
|
||||
Messages::badValueForDefaultArgMessage($varName, $typeName, $val),
|
||||
DefaultValuesOfCorrectType::badValueForDefaultArgMessage($varName, $typeName, $val, $realErrors),
|
||||
[ new SourceLocation($line, $column) ]
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
||||
class FieldsOnCorrectTypeTest extends TestCase
|
||||
{
|
||||
// Validate: Fields on correct type
|
||||
|
||||
/**
|
||||
* @it Object field selection
|
||||
*/
|
||||
public function testObjectFieldSelection()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType(), '
|
||||
@ -19,6 +23,9 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Aliased object field selection
|
||||
*/
|
||||
public function testAliasedObjectFieldSelection()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -29,6 +36,9 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Interface field selection
|
||||
*/
|
||||
public function testInterfaceFieldSelection()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -39,6 +49,9 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Aliased interface field selection
|
||||
*/
|
||||
public function testAliasedInterfaceFieldSelection()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -48,6 +61,9 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Lying alias selection
|
||||
*/
|
||||
public function testLyingAliasSelection()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -57,6 +73,9 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Ignores fields on unknown type
|
||||
*/
|
||||
public function testIgnoresFieldsOnUnknownType()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -66,17 +85,41 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it reports errors when type is known again
|
||||
*/
|
||||
public function testReportsErrorsWhenTypeIsKnownAgain()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment typeKnownAgain on Pet {
|
||||
unknown_pet_field {
|
||||
... on Cat {
|
||||
unknown_cat_field
|
||||
}
|
||||
}
|
||||
}',
|
||||
[ $this->undefinedField('unknown_pet_field', 'Pet', [], 3, 9),
|
||||
$this->undefinedField('unknown_cat_field', 'Cat', [], 5, 13) ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Field not defined on fragment
|
||||
*/
|
||||
public function testFieldNotDefinedOnFragment()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment fieldNotDefined on Dog {
|
||||
meowVolume
|
||||
}',
|
||||
[$this->undefinedField('meowVolume', 'Dog', 3, 9)]
|
||||
[$this->undefinedField('meowVolume', 'Dog', [], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
public function testFieldNotDefinedDeeplyOnlyReportsFirst()
|
||||
/**
|
||||
* @it Ignores deeply unknown field
|
||||
*/
|
||||
public function testIgnoresDeeplyUnknownField()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment deepFieldNotDefined on Dog {
|
||||
@ -84,10 +127,13 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
deeper_unknown_field
|
||||
}
|
||||
}',
|
||||
[$this->undefinedField('unknown_field', 'Dog', 3, 9)]
|
||||
[$this->undefinedField('unknown_field', 'Dog', [], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Sub-field not defined
|
||||
*/
|
||||
public function testSubFieldNotDefined()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
@ -96,10 +142,13 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
unknown_field
|
||||
}
|
||||
}',
|
||||
[$this->undefinedField('unknown_field', 'Pet', 4, 11)]
|
||||
[$this->undefinedField('unknown_field', 'Pet', [], 4, 11)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Field not defined on inline fragment
|
||||
*/
|
||||
public function testFieldNotDefinedOnInlineFragment()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
@ -108,50 +157,65 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
meowVolume
|
||||
}
|
||||
}',
|
||||
[$this->undefinedField('meowVolume', 'Dog', 4, 11)]
|
||||
[$this->undefinedField('meowVolume', 'Dog', [], 4, 11)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Aliased field target not defined
|
||||
*/
|
||||
public function testAliasedFieldTargetNotDefined()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment aliasedFieldTargetNotDefined on Dog {
|
||||
volume : mooVolume
|
||||
}',
|
||||
[$this->undefinedField('mooVolume', 'Dog', 3, 9)]
|
||||
[$this->undefinedField('mooVolume', 'Dog', [], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Aliased lying field target not defined
|
||||
*/
|
||||
public function testAliasedLyingFieldTargetNotDefined()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment aliasedLyingFieldTargetNotDefined on Dog {
|
||||
barkVolume : kawVolume
|
||||
}',
|
||||
[$this->undefinedField('kawVolume', 'Dog', 3, 9)]
|
||||
[$this->undefinedField('kawVolume', 'Dog', [], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Not defined on interface
|
||||
*/
|
||||
public function testNotDefinedOnInterface()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment notDefinedOnInterface on Pet {
|
||||
tailLength
|
||||
}',
|
||||
[$this->undefinedField('tailLength', 'Pet', 3, 9)]
|
||||
[$this->undefinedField('tailLength', 'Pet', [], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Defined on implementors but not on interface
|
||||
*/
|
||||
public function testDefinedOnImplmentorsButNotOnInterface()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment definedOnImplementorsButNotInterface on Pet {
|
||||
nickname
|
||||
}',
|
||||
[$this->undefinedField('nickname', 'Pet', 3, 9)]
|
||||
[$this->undefinedField('nickname', 'Pet', [ 'Cat', 'Dog' ], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Meta field selection on union
|
||||
*/
|
||||
public function testMetaFieldSelectionOnUnion()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -161,26 +225,35 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Direct field selection on union
|
||||
*/
|
||||
public function testDirectFieldSelectionOnUnion()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment directFieldSelectionOnUnion on CatOrDog {
|
||||
directField
|
||||
}',
|
||||
[$this->undefinedField('directField', 'CatOrDog', 3, 9)]
|
||||
[$this->undefinedField('directField', 'CatOrDog', [], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Defined on implementors queried on union
|
||||
*/
|
||||
public function testDefinedOnImplementorsQueriedOnUnion()
|
||||
{
|
||||
$this->expectFailsRule(new FieldsOnCorrectType, '
|
||||
fragment definedOnImplementorsQueriedOnUnion on CatOrDog {
|
||||
name
|
||||
}',
|
||||
[$this->undefinedField('name', 'CatOrDog', 3, 9)]
|
||||
[$this->undefinedField('name', 'CatOrDog', [ 'Being', 'Pet', 'Canine', 'Cat', 'Dog' ], 3, 9)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it valid field in inline fragment
|
||||
*/
|
||||
public function testValidFieldInInlineFragment()
|
||||
{
|
||||
$this->expectPassesRule(new FieldsOnCorrectType, '
|
||||
@ -192,10 +265,45 @@ class FieldsOnCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
private function undefinedField($field, $type, $line, $column)
|
||||
// Describe: Fields on correct type error message
|
||||
|
||||
/**
|
||||
* @it Works with no suggestions
|
||||
*/
|
||||
public function testWorksWithNoSuggestions()
|
||||
{
|
||||
$this->assertEquals('Cannot query field "T" on type "f".', FieldsOnCorrectType::undefinedFieldMessage('T', 'f', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Works with no small numbers of suggestions
|
||||
*/
|
||||
public function testWorksWithNoSmallNumbersOfSuggestions()
|
||||
{
|
||||
$expected = 'Cannot query field "T" on type "f". ' .
|
||||
'However, this field exists on "A", "B". ' .
|
||||
'Perhaps you meant to use an inline fragment?';
|
||||
|
||||
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('T', 'f', [ 'A', 'B' ]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Works with lots of suggestions
|
||||
*/
|
||||
public function testWorksWithLotsOfSuggestions()
|
||||
{
|
||||
$expected = 'Cannot query field "T" on type "f". ' .
|
||||
'However, this field exists on "A", "B", "C", "D", "E", ' .
|
||||
'and 1 other types. ' .
|
||||
'Perhaps you meant to use an inline fragment?';
|
||||
|
||||
$this->assertEquals($expected, FieldsOnCorrectType::undefinedFieldMessage('T', 'f', [ 'A', 'B', 'C', 'D', 'E', 'F' ]));
|
||||
}
|
||||
|
||||
private function undefinedField($field, $type, $suggestions, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
Messages::undefinedFieldMessage($field, $type),
|
||||
FieldsOnCorrectType::undefinedFieldMessage($field, $type, $suggestions),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
{
|
||||
// Validate: Fragments on composite types
|
||||
|
||||
/**
|
||||
* @it object is valid fragment type
|
||||
*/
|
||||
public function testObjectIsValidFragmentType()
|
||||
{
|
||||
$this->expectPassesRule(new FragmentsOnCompositeTypes, '
|
||||
@ -18,6 +21,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it interface is valid fragment type
|
||||
*/
|
||||
public function testInterfaceIsValidFragmentType()
|
||||
{
|
||||
$this->expectPassesRule(new FragmentsOnCompositeTypes, '
|
||||
@ -27,6 +33,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it object is valid inline fragment type
|
||||
*/
|
||||
public function testObjectIsValidInlineFragmentType()
|
||||
{
|
||||
$this->expectPassesRule(new FragmentsOnCompositeTypes, '
|
||||
@ -38,6 +47,23 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it inline fragment without type is valid
|
||||
*/
|
||||
public function testInlineFragmentWithoutTypeIsValid()
|
||||
{
|
||||
$this->expectPassesRule(new FragmentsOnCompositeTypes, '
|
||||
fragment validFragment on Pet {
|
||||
... {
|
||||
name
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it union is valid fragment type
|
||||
*/
|
||||
public function testUnionIsValidFragmentType()
|
||||
{
|
||||
$this->expectPassesRule(new FragmentsOnCompositeTypes, '
|
||||
@ -47,6 +73,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it scalar is invalid fragment type
|
||||
*/
|
||||
public function testScalarIsInvalidFragmentType()
|
||||
{
|
||||
$this->expectFailsRule(new FragmentsOnCompositeTypes, '
|
||||
@ -57,6 +86,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
[$this->error('scalarFragment', 'Boolean', 2, 34)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it enum is invalid fragment type
|
||||
*/
|
||||
public function testEnumIsInvalidFragmentType()
|
||||
{
|
||||
$this->expectFailsRule(new FragmentsOnCompositeTypes, '
|
||||
@ -67,6 +99,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
[$this->error('scalarFragment', 'FurColor', 2, 34)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it input object is invalid fragment type
|
||||
*/
|
||||
public function testInputObjectIsInvalidFragmentType()
|
||||
{
|
||||
$this->expectFailsRule(new FragmentsOnCompositeTypes, '
|
||||
@ -77,6 +112,9 @@ class FragmentsOnCompositeTypesTest extends TestCase
|
||||
[$this->error('inputFragment', 'ComplexInput', 2, 33)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it scalar is invalid inline fragment type
|
||||
*/
|
||||
public function testScalarIsInvalidInlineFragmentType()
|
||||
{
|
||||
$this->expectFailsRule(new FragmentsOnCompositeTypes, '
|
||||
|
@ -8,6 +8,10 @@ use GraphQL\Validator\Rules\KnownArgumentNames;
|
||||
class KnownArgumentNamesTest extends TestCase
|
||||
{
|
||||
// Validate: Known argument names:
|
||||
|
||||
/**
|
||||
* @it single arg is known
|
||||
*/
|
||||
public function testSingleArgIsKnown()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -17,6 +21,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple args are known
|
||||
*/
|
||||
public function testMultipleArgsAreKnown()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -26,6 +33,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it ignores args of unknown fields
|
||||
*/
|
||||
public function testIgnoresArgsOfUnknownFields()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -35,6 +45,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple args in reverse order are known
|
||||
*/
|
||||
public function testMultipleArgsInReverseOrderAreKnown()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -44,6 +57,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no args on optional arg
|
||||
*/
|
||||
public function testNoArgsOnOptionalArg()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -53,6 +69,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it args are known deeply
|
||||
*/
|
||||
public function testArgsAreKnownDeeply()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -71,6 +90,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it directive args are known
|
||||
*/
|
||||
public function testDirectiveArgsAreKnown()
|
||||
{
|
||||
$this->expectPassesRule(new KnownArgumentNames, '
|
||||
@ -80,6 +102,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it undirective args are invalid
|
||||
*/
|
||||
public function testUndirectiveArgsAreInvalid()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
@ -91,6 +116,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it invalid arg name
|
||||
*/
|
||||
public function testInvalidArgName()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
@ -102,6 +130,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it unknown args amongst known args
|
||||
*/
|
||||
public function testUnknownArgsAmongstKnownArgs()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
@ -114,6 +145,9 @@ class KnownArgumentNamesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it unknown args deeply
|
||||
*/
|
||||
public function testUnknownArgsDeeply()
|
||||
{
|
||||
$this->expectFailsRule(new KnownArgumentNames, '
|
||||
|
@ -8,6 +8,10 @@ use GraphQL\Validator\Rules\KnownDirectives;
|
||||
class KnownDirectivesTest extends TestCase
|
||||
{
|
||||
// Validate: Known directives
|
||||
|
||||
/**
|
||||
* @it with no directives
|
||||
*/
|
||||
public function testWithNoDirectives()
|
||||
{
|
||||
$this->expectPassesRule(new KnownDirectives, '
|
||||
@ -22,6 +26,9 @@ class KnownDirectivesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it with known directives
|
||||
*/
|
||||
public function testWithKnownDirectives()
|
||||
{
|
||||
$this->expectPassesRule(new KnownDirectives, '
|
||||
@ -36,6 +43,9 @@ class KnownDirectivesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it with unknown directive
|
||||
*/
|
||||
public function testWithUnknownDirective()
|
||||
{
|
||||
$this->expectFailsRule(new KnownDirectives, '
|
||||
@ -49,6 +59,9 @@ class KnownDirectivesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it with many unknown directives
|
||||
*/
|
||||
public function testWithManyUnknownDirectives()
|
||||
{
|
||||
$this->expectFailsRule(new KnownDirectives, '
|
||||
@ -70,6 +83,9 @@ class KnownDirectivesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it with well placed directives
|
||||
*/
|
||||
public function testWithWellPlacedDirectives()
|
||||
{
|
||||
$this->expectPassesRule(new KnownDirectives, '
|
||||
@ -82,15 +98,20 @@ class KnownDirectivesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it with misplaced directives
|
||||
*/
|
||||
public function testWithMisplacedDirectives()
|
||||
{
|
||||
$this->expectFailsRule(new KnownDirectives, '
|
||||
query Foo @include(if: true) {
|
||||
name
|
||||
...Frag
|
||||
name @operationOnly
|
||||
...Frag @operationOnly
|
||||
}
|
||||
', [
|
||||
$this->misplacedDirective('include', 'operation', 2, 17)
|
||||
$this->misplacedDirective('include', 'QUERY', 2, 17),
|
||||
$this->misplacedDirective('operationOnly', 'FIELD', 3, 14),
|
||||
$this->misplacedDirective('operationOnly', 'FRAGMENT_SPREAD', 4, 17),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,9 @@ class KnownFragmentNamesTest extends TestCase
|
||||
{
|
||||
// Validate: Known fragment names
|
||||
|
||||
/**
|
||||
* @it known fragment names are valid
|
||||
*/
|
||||
public function testKnownFragmentNamesAreValid()
|
||||
{
|
||||
$this->expectPassesRule(new KnownFragmentNames, '
|
||||
@ -33,6 +36,9 @@ class KnownFragmentNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it unknown fragment names are invalid
|
||||
*/
|
||||
public function testUnknownFragmentNamesAreInvalid()
|
||||
{
|
||||
$this->expectFailsRule(new KnownFragmentNames, '
|
||||
|
@ -9,6 +9,9 @@ class KnownTypeNamesTest extends TestCase
|
||||
{
|
||||
// Validate: Known type names
|
||||
|
||||
/**
|
||||
* @it known type names are valid
|
||||
*/
|
||||
public function testKnownTypeNamesAreValid()
|
||||
{
|
||||
$this->expectPassesRule(new KnownTypeNames, '
|
||||
@ -23,6 +26,9 @@ class KnownTypeNamesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it unknown type names are invalid
|
||||
*/
|
||||
public function testUnknownTypeNamesAreInvalid()
|
||||
{
|
||||
$this->expectFailsRule(new KnownTypeNames, '
|
||||
@ -42,6 +48,32 @@ class KnownTypeNamesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it ignores type definitions
|
||||
*/
|
||||
public function testIgnoresTypeDefinitions()
|
||||
{
|
||||
$this->expectFailsRule(new KnownTypeNames, '
|
||||
type NotInTheSchema {
|
||||
field: FooBar
|
||||
}
|
||||
interface FooBar {
|
||||
field: NotInTheSchema
|
||||
}
|
||||
union U = A | B
|
||||
input Blob {
|
||||
field: UnknownType
|
||||
}
|
||||
query Foo($var: NotInTheSchema) {
|
||||
user(id: $var) {
|
||||
id
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->unknownType('NotInTheSchema', 12, 23),
|
||||
]);
|
||||
}
|
||||
|
||||
private function unknownType($typeName, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
|
127
tests/Validator/LoneAnonymousOperationTest.php
Normal file
127
tests/Validator/LoneAnonymousOperationTest.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Validator;
|
||||
|
||||
use GraphQL\FormattedError;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Validator\Rules\LoneAnonymousOperation;
|
||||
|
||||
class LoneAnonymousOperationTest extends TestCase
|
||||
{
|
||||
// Validate: Anonymous operation must be alone
|
||||
|
||||
/**
|
||||
* @it no operations
|
||||
*/
|
||||
public function testNoOperations()
|
||||
{
|
||||
$this->expectPassesRule(new LoneAnonymousOperation, '
|
||||
fragment fragA on Type {
|
||||
field
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it one anon operation
|
||||
*/
|
||||
public function testOneAnonOperation()
|
||||
{
|
||||
$this->expectPassesRule(new LoneAnonymousOperation, '
|
||||
{
|
||||
field
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple named operations
|
||||
*/
|
||||
public function testMultipleNamedOperations()
|
||||
{
|
||||
$this->expectPassesRule(new LoneAnonymousOperation, '
|
||||
query Foo {
|
||||
field
|
||||
}
|
||||
|
||||
query Bar {
|
||||
field
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it anon operation with fragment
|
||||
*/
|
||||
public function testAnonOperationWithFragment()
|
||||
{
|
||||
$this->expectPassesRule(new LoneAnonymousOperation, '
|
||||
{
|
||||
...Foo
|
||||
}
|
||||
fragment Foo on Type {
|
||||
field
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple anon operations
|
||||
*/
|
||||
public function testMultipleAnonOperations()
|
||||
{
|
||||
$this->expectFailsRule(new LoneAnonymousOperation, '
|
||||
{
|
||||
fieldA
|
||||
}
|
||||
{
|
||||
fieldB
|
||||
}
|
||||
', [
|
||||
$this->anonNotAlone(2, 7),
|
||||
$this->anonNotAlone(5, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it anon operation with a mutation
|
||||
*/
|
||||
public function testAnonOperationWithMutation()
|
||||
{
|
||||
$this->expectFailsRule(new LoneAnonymousOperation, '
|
||||
{
|
||||
fieldA
|
||||
}
|
||||
mutation Foo {
|
||||
fieldB
|
||||
}
|
||||
', [
|
||||
$this->anonNotAlone(2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it anon operation with a subscription
|
||||
*/
|
||||
public function testAnonOperationWithSubscription()
|
||||
{
|
||||
$this->expectFailsRule(new LoneAnonymousOperation, '
|
||||
{
|
||||
fieldA
|
||||
}
|
||||
subscription Foo {
|
||||
fieldB
|
||||
}
|
||||
', [
|
||||
$this->anonNotAlone(2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
private function anonNotAlone($line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
LoneAnonymousOperation::anonOperationNotAloneMessage(),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@ -9,6 +9,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
{
|
||||
// Validate: No circular fragment spreads
|
||||
|
||||
/**
|
||||
* @it single reference is valid
|
||||
*/
|
||||
public function testSingleReferenceIsValid()
|
||||
{
|
||||
$this->expectPassesRule(new NoFragmentCycles(), '
|
||||
@ -17,6 +20,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it spreading twice is not circular
|
||||
*/
|
||||
public function testSpreadingTwiceIsNotCircular()
|
||||
{
|
||||
$this->expectPassesRule(new NoFragmentCycles, '
|
||||
@ -25,6 +31,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it spreading twice indirectly is not circular
|
||||
*/
|
||||
public function testSpreadingTwiceIndirectlyIsNotCircular()
|
||||
{
|
||||
$this->expectPassesRule(new NoFragmentCycles, '
|
||||
@ -34,6 +43,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it double spread within abstract types
|
||||
*/
|
||||
public function testDoubleSpreadWithinAbstractTypes()
|
||||
{
|
||||
$this->expectPassesRule(new NoFragmentCycles, '
|
||||
@ -49,6 +61,21 @@ class NoFragmentCyclesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it does not false positive on unknown fragment
|
||||
*/
|
||||
public function testDoesNotFalsePositiveOnUnknownFragment()
|
||||
{
|
||||
$this->expectPassesRule(new NoFragmentCycles, '
|
||||
fragment nameFragment on Pet {
|
||||
...UnknownFragment
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it spreading recursively within field fails
|
||||
*/
|
||||
public function testSpreadingRecursivelyWithinFieldFails()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -58,6 +85,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself directly
|
||||
*/
|
||||
public function testNoSpreadingItselfDirectly()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -67,6 +97,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself directly within inline fragment
|
||||
*/
|
||||
public function testNoSpreadingItselfDirectlyWithinInlineFragment()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -80,6 +113,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself indirectly
|
||||
*/
|
||||
public function testNoSpreadingItselfIndirectly()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -93,6 +129,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself indirectly reports opposite order
|
||||
*/
|
||||
public function testNoSpreadingItselfIndirectlyReportsOppositeOrder()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -106,6 +145,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself indirectly within inline fragment
|
||||
*/
|
||||
public function testNoSpreadingItselfIndirectlyWithinInlineFragment()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -127,6 +169,9 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself deeply
|
||||
*/
|
||||
public function testNoSpreadingItselfDeeply()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
@ -136,30 +181,36 @@ class NoFragmentCyclesTest extends TestCase
|
||||
fragment fragX on Dog { ...fragY }
|
||||
fragment fragY on Dog { ...fragZ }
|
||||
fragment fragZ on Dog { ...fragO }
|
||||
fragment fragO on Dog { ...fragA, ...fragX }
|
||||
fragment fragO on Dog { ...fragP }
|
||||
fragment fragP on Dog { ...fragA, ...fragX }
|
||||
', [
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', ['fragB', 'fragC', 'fragO']),
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', [ 'fragB', 'fragC', 'fragO', 'fragP' ]),
|
||||
[
|
||||
new SourceLocation(2, 31),
|
||||
new SourceLocation(3, 31),
|
||||
new SourceLocation(4, 31),
|
||||
new SourceLocation(8, 31),
|
||||
new SourceLocation(9, 31),
|
||||
]
|
||||
),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragX', ['fragY', 'fragZ', 'fragO']),
|
||||
NoFragmentCycles::cycleErrorMessage('fragO', [ 'fragP', 'fragX', 'fragY', 'fragZ' ]),
|
||||
[
|
||||
new SourceLocation(8, 31),
|
||||
new SourceLocation(9, 41),
|
||||
new SourceLocation(5, 31),
|
||||
new SourceLocation(6, 31),
|
||||
new SourceLocation(7, 31),
|
||||
new SourceLocation(8, 41),
|
||||
]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
public function testNoSpreadingItselfDeeplyTwoPathsNewRule()
|
||||
/**
|
||||
* @it no spreading itself deeply two paths
|
||||
*/
|
||||
public function testNoSpreadingItselfDeeplyTwoPaths()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
fragment fragA on Dog { ...fragB, ...fragC }
|
||||
@ -177,6 +228,56 @@ class NoFragmentCyclesTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself deeply two paths -- alt traverse order
|
||||
*/
|
||||
public function testNoSpreadingItselfDeeplyTwoPathsTraverseOrder()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
fragment fragA on Dog { ...fragC }
|
||||
fragment fragB on Dog { ...fragC }
|
||||
fragment fragC on Dog { ...fragA, ...fragB }
|
||||
', [
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', [ 'fragC' ]),
|
||||
[new SourceLocation(2,31), new SourceLocation(4,31)]
|
||||
),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragC', [ 'fragB' ]),
|
||||
[new SourceLocation(4, 41), new SourceLocation(3, 31)]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it no spreading itself deeply and immediately
|
||||
*/
|
||||
public function testNoSpreadingItselfDeeplyAndImmediately()
|
||||
{
|
||||
$this->expectFailsRule(new NoFragmentCycles, '
|
||||
fragment fragA on Dog { ...fragB }
|
||||
fragment fragB on Dog { ...fragB, ...fragC }
|
||||
fragment fragC on Dog { ...fragA, ...fragB }
|
||||
', [
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragB', []),
|
||||
[new SourceLocation(3, 31)]
|
||||
),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragA', [ 'fragB', 'fragC' ]),
|
||||
[
|
||||
new SourceLocation(2, 31),
|
||||
new SourceLocation(3, 41),
|
||||
new SourceLocation(4, 31)
|
||||
]
|
||||
),
|
||||
FormattedError::create(
|
||||
NoFragmentCycles::cycleErrorMessage('fragB', [ 'fragC' ]),
|
||||
[new SourceLocation(3, 41), new SourceLocation(4, 41)]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
private function cycleError($fargment, $spreadNames, $line, $column)
|
||||
{
|
||||
return FormattedError::create(
|
||||
|
@ -9,6 +9,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
{
|
||||
// Validate: No undefined variables
|
||||
|
||||
/**
|
||||
* @it all variables defined
|
||||
*/
|
||||
public function testAllVariablesDefined()
|
||||
{
|
||||
$this->expectPassesRule(new NoUndefinedVariables(), '
|
||||
@ -18,6 +21,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it all variables deeply defined
|
||||
*/
|
||||
public function testAllVariablesDeeplyDefined()
|
||||
{
|
||||
$this->expectPassesRule(new NoUndefinedVariables, '
|
||||
@ -31,6 +37,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it all variables deeply in inline fragments defined
|
||||
*/
|
||||
public function testAllVariablesDeeplyInInlineFragmentsDefined()
|
||||
{
|
||||
$this->expectPassesRule(new NoUndefinedVariables, '
|
||||
@ -48,6 +57,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it all variables in fragments deeply defined
|
||||
*/
|
||||
public function testAllVariablesInFragmentsDeeplyDefined()
|
||||
{
|
||||
$this->expectPassesRule(new NoUndefinedVariables, '
|
||||
@ -70,6 +82,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable within single fragment defined in multiple operations
|
||||
*/
|
||||
public function testVariableWithinSingleFragmentDefinedInMultipleOperations()
|
||||
{
|
||||
// variable within single fragment defined in multiple operations
|
||||
@ -86,6 +101,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable within fragments defined in operations
|
||||
*/
|
||||
public function testVariableWithinFragmentsDefinedInOperations()
|
||||
{
|
||||
$this->expectPassesRule(new NoUndefinedVariables, '
|
||||
@ -104,6 +122,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable within recursive fragment defined
|
||||
*/
|
||||
public function testVariableWithinRecursiveFragmentDefined()
|
||||
{
|
||||
$this->expectPassesRule(new NoUndefinedVariables, '
|
||||
@ -118,6 +139,9 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable not defined
|
||||
*/
|
||||
public function testVariableNotDefined()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -125,10 +149,13 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(a: $a, b: $b, c: $c, d: $d)
|
||||
}
|
||||
', [
|
||||
$this->undefVar('d', 3, 39)
|
||||
$this->undefVar('d', 3, 39, 'Foo', 2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable not defined by un-named query
|
||||
*/
|
||||
public function testVariableNotDefinedByUnNamedQuery()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -136,10 +163,13 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(a: $a)
|
||||
}
|
||||
', [
|
||||
$this->undefVar('a', 3, 18)
|
||||
$this->undefVar('a', 3, 18, '', 2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple variables not defined
|
||||
*/
|
||||
public function testMultipleVariablesNotDefined()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -147,11 +177,14 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(a: $a, b: $b, c: $c)
|
||||
}
|
||||
', [
|
||||
$this->undefVar('a', 3, 18),
|
||||
$this->undefVar('c', 3, 32)
|
||||
$this->undefVar('a', 3, 18, 'Foo', 2, 7),
|
||||
$this->undefVar('c', 3, 32, 'Foo', 2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable in fragment not defined by un-named query
|
||||
*/
|
||||
public function testVariableInFragmentNotDefinedByUnNamedQuery()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -162,10 +195,13 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(a: $a)
|
||||
}
|
||||
', [
|
||||
$this->undefVar('a', 6, 18)
|
||||
$this->undefVar('a', 6, 18, '', 2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable in fragment not defined by operation
|
||||
*/
|
||||
public function testVariableInFragmentNotDefinedByOperation()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -186,10 +222,13 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(c: $c)
|
||||
}
|
||||
', [
|
||||
$this->undefVarByOp('c', 16, 18, 'Foo', 2, 7)
|
||||
$this->undefVar('c', 16, 18, 'Foo', 2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple variables in fragments not defined
|
||||
*/
|
||||
public function testMultipleVariablesInFragmentsNotDefined()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -210,11 +249,14 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(c: $c)
|
||||
}
|
||||
', [
|
||||
$this->undefVarByOp('a', 6, 18, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('c', 16, 18, 'Foo', 2, 7)
|
||||
$this->undefVar('a', 6, 18, 'Foo', 2, 7),
|
||||
$this->undefVar('c', 16, 18, 'Foo', 2, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it single variable in fragment not defined by multiple operations
|
||||
*/
|
||||
public function testSingleVariableInFragmentNotDefinedByMultipleOperations()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -228,11 +270,14 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(a: $a, b: $b)
|
||||
}
|
||||
', [
|
||||
$this->undefVarByOp('b', 9, 25, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('b', 9, 25, 'Bar', 5, 7)
|
||||
$this->undefVar('b', 9, 25, 'Foo', 2, 7),
|
||||
$this->undefVar('b', 9, 25, 'Bar', 5, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variables in fragment not defined by multiple operations
|
||||
*/
|
||||
public function testVariablesInFragmentNotDefinedByMultipleOperations()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -246,11 +291,14 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(a: $a, b: $b)
|
||||
}
|
||||
', [
|
||||
$this->undefVarByOp('a', 9, 18, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('b', 9, 25, 'Bar', 5, 7)
|
||||
$this->undefVar('a', 9, 18, 'Foo', 2, 7),
|
||||
$this->undefVar('b', 9, 25, 'Bar', 5, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it variable in fragment used by other operation
|
||||
*/
|
||||
public function testVariableInFragmentUsedByOtherOperation()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -267,11 +315,14 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field(b: $b)
|
||||
}
|
||||
', [
|
||||
$this->undefVarByOp('a', 9, 18, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('b', 12, 18, 'Bar', 5, 7)
|
||||
$this->undefVar('a', 9, 18, 'Foo', 2, 7),
|
||||
$this->undefVar('b', 12, 18, 'Bar', 5, 7)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it multiple undefined variables produce multiple errors
|
||||
*/
|
||||
public function testMultipleUndefinedVariablesProduceMultipleErrors()
|
||||
{
|
||||
$this->expectFailsRule(new NoUndefinedVariables, '
|
||||
@ -290,29 +341,27 @@ class NoUndefinedVariablesTest extends TestCase
|
||||
field2(c: $c)
|
||||
}
|
||||
', [
|
||||
$this->undefVarByOp('a', 9, 19, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('c', 14, 19, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('a', 11, 19, 'Foo', 2, 7),
|
||||
$this->undefVarByOp('b', 9, 26, 'Bar', 5, 7),
|
||||
$this->undefVarByOp('c', 14, 19, 'Bar', 5, 7),
|
||||
$this->undefVarByOp('b', 11, 26, 'Bar', 5, 7),
|
||||
$this->undefVar('a', 9, 19, 'Foo', 2, 7),
|
||||
$this->undefVar('a', 11, 19, 'Foo', 2, 7),
|
||||
$this->undefVar('c', 14, 19, 'Foo', 2, 7),
|
||||
$this->undefVar('b', 9, 26, 'Bar', 5, 7),
|
||||
$this->undefVar('b', 11, 26, 'Bar', 5, 7),
|
||||
$this->undefVar('c', 14, 19, 'Bar', 5, 7),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private function undefVar($varName, $line, $column)
|
||||
private function undefVar($varName, $line, $column, $opName = null, $l2 = null, $c2 = null)
|
||||
{
|
||||
return FormattedError::create(
|
||||
NoUndefinedVariables::undefinedVarMessage($varName),
|
||||
[new SourceLocation($line, $column)]
|
||||
);
|
||||
$locs = [new SourceLocation($line, $column)];
|
||||
|
||||
if ($l2 && $c2) {
|
||||
$locs[] = new SourceLocation($l2, $c2);
|
||||
}
|
||||
|
||||
private function undefVarByOp($varName, $l1, $c1, $opName, $l2, $c2)
|
||||
{
|
||||
return FormattedError::create(
|
||||
NoUndefinedVariables::undefinedVarByOpMessage($varName, $opName),
|
||||
[new SourceLocation($l1, $c1), new SourceLocation($l2, $c2)]
|
||||
NoUndefinedVariables::undefinedVarMessage($varName, $opName),
|
||||
$locs
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,10 @@ use GraphQL\Validator\Rules\NoUnusedFragments;
|
||||
class NoUnusedFragmentsTest extends TestCase
|
||||
{
|
||||
// Validate: No unused fragments
|
||||
|
||||
/**
|
||||
* @it all fragment names are used
|
||||
*/
|
||||
public function testAllFragmentNamesAreUsed()
|
||||
{
|
||||
$this->expectPassesRule(new NoUnusedFragments(), '
|
||||
@ -32,6 +36,9 @@ class NoUnusedFragmentsTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it all fragment names are used by multiple operations
|
||||
*/
|
||||
public function testAllFragmentNamesAreUsedByMultipleOperations()
|
||||
{
|
||||
$this->expectPassesRule(new NoUnusedFragments, '
|
||||
@ -58,6 +65,9 @@ class NoUnusedFragmentsTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it contains unknown fragments
|
||||
*/
|
||||
public function testContainsUnknownFragments()
|
||||
{
|
||||
$this->expectFailsRule(new NoUnusedFragments, '
|
||||
@ -93,6 +103,9 @@ class NoUnusedFragmentsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it contains unknown fragments with ref cycle
|
||||
*/
|
||||
public function testContainsUnknownFragmentsWithRefCycle()
|
||||
{
|
||||
$this->expectFailsRule(new NoUnusedFragments, '
|
||||
@ -130,6 +143,9 @@ class NoUnusedFragmentsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it contains unknown and undef fragments
|
||||
*/
|
||||
public function testContainsUnknownAndUndefFragments()
|
||||
{
|
||||
|
||||
|
@ -3,6 +3,7 @@ namespace GraphQL\Tests\Validator;
|
||||
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
@ -43,12 +44,24 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
],
|
||||
]);
|
||||
|
||||
$Canine = new InterfaceType([
|
||||
'name' => 'Canine',
|
||||
'fields' => function() {
|
||||
return [
|
||||
'name' => [
|
||||
'type' => Type::string(),
|
||||
'args' => ['surname' => ['type' => Type::boolean()]]
|
||||
]
|
||||
];
|
||||
}
|
||||
]);
|
||||
|
||||
$DogCommand = new EnumType([
|
||||
'name' => 'DogCommand',
|
||||
'values' => [
|
||||
'SIT' => ['value' => 0],
|
||||
'HEEL' => ['value' => 1],
|
||||
'DOWN' => ['value' => 3]
|
||||
'DOWN' => ['value' => 2]
|
||||
]
|
||||
]);
|
||||
|
||||
@ -76,7 +89,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
'args' => ['x' => ['type' => Type::int()], 'y' => ['type' => Type::int()]]
|
||||
]
|
||||
],
|
||||
'interfaces' => [$Being, $Pet]
|
||||
'interfaces' => [$Being, $Pet, $Canine]
|
||||
]);
|
||||
|
||||
$Cat = new ObjectType([
|
||||
@ -277,7 +290,15 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
||||
]
|
||||
]);
|
||||
|
||||
$defaultSchema = new Schema($queryRoot);
|
||||
$defaultSchema = new Schema([
|
||||
'query' => $queryRoot,
|
||||
'directives' => [
|
||||
new Directive([
|
||||
'name' => 'operationOnly',
|
||||
'locations' => [ 'QUERY' ],
|
||||
])
|
||||
]
|
||||
]);
|
||||
return $defaultSchema;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user