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