mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 14:26:08 +03:00
Schema validation + tests (#148)
This commit is contained in:
parent
d3580e959e
commit
34eae0b891
@ -64,19 +64,6 @@ $episodeEnum = new EnumType([
|
||||
|
||||
which is equivalent of:
|
||||
```php
|
||||
$episodeEnum = new EnumType([
|
||||
'name' => 'Episode',
|
||||
'description' => 'One of the films in the Star Wars Trilogy',
|
||||
'values' => [
|
||||
'NEWHOPE' => 'NEWHOPE',
|
||||
'EMPIRE' => 'EMPIRE',
|
||||
'JEDI' => 'JEDI'
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
which is in turn equivalent of:
|
||||
```php
|
||||
$episodeEnum = new EnumType([
|
||||
'name' => 'Episode',
|
||||
'description' => 'One of the films in the Star Wars Trilogy',
|
||||
|
@ -14,6 +14,13 @@ final class Warning
|
||||
|
||||
static $warned = [];
|
||||
|
||||
static private $warningHandler;
|
||||
|
||||
public static function setWarningHandler(callable $warningHandler = null)
|
||||
{
|
||||
self::$warningHandler = $warningHandler;
|
||||
}
|
||||
|
||||
static function suppress($suppress = true)
|
||||
{
|
||||
if (true === $suppress) {
|
||||
@ -40,7 +47,10 @@ final class Warning
|
||||
|
||||
static function warnOnce($errorMessage, $warningId)
|
||||
{
|
||||
if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
|
||||
if (self::$warningHandler) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
|
||||
self::$warned[$warningId] = true;
|
||||
trigger_error($errorMessage, E_USER_WARNING);
|
||||
}
|
||||
@ -48,7 +58,10 @@ final class Warning
|
||||
|
||||
static function warn($errorMessage, $warningId)
|
||||
{
|
||||
if ((self::$enableWarnings & $warningId) > 0) {
|
||||
if (self::$warningHandler) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} else if ((self::$enableWarnings & $warningId) > 0) {
|
||||
trigger_error($errorMessage, E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
* Class CustomScalarType
|
||||
* @package GraphQL\Type\Definition
|
||||
@ -38,7 +40,11 @@ class CustomScalarType extends ScalarType
|
||||
*/
|
||||
public function parseValue($value)
|
||||
{
|
||||
if (isset($this->config['parseValue'])) {
|
||||
return call_user_func($this->config['parseValue'], $value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,6 +53,29 @@ class CustomScalarType extends ScalarType
|
||||
*/
|
||||
public function parseLiteral(/* GraphQL\Language\AST\ValueNode */ $valueNode)
|
||||
{
|
||||
if (isset($this->config['parseLiteral'])) {
|
||||
return call_user_func($this->config['parseLiteral'], $valueNode);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function assertValid()
|
||||
{
|
||||
parent::assertValid();
|
||||
|
||||
Utils::invariant(
|
||||
isset($this->config['serialize']) && is_callable($this->config['serialize']),
|
||||
"{$this->name} must provide \"serialize\" function. If this custom Scalar " .
|
||||
'is also used as an input type, ensure "parseValue" and "parseLiteral" ' .
|
||||
'functions are also provided.'
|
||||
);
|
||||
if (isset($this->config['parseValue']) || isset($this->config['parseLiteral'])) {
|
||||
Utils::invariant(
|
||||
isset($this->config['parseValue']) && isset($this->config['parseLiteral']) &&
|
||||
is_callable($this->config['parseValue']) && is_callable($this->config['parseLiteral']),
|
||||
"{$this->name} must provide both \"parseValue\" and \"parseLiteral\" functions."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Language\AST\EnumValueNode;
|
||||
use GraphQL\Utils\MixedStore;
|
||||
use GraphQL\Utils\Utils;
|
||||
@ -26,6 +27,11 @@ class EnumType extends Type implements InputType, OutputType, LeafType
|
||||
*/
|
||||
private $nameLookup;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $config;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
if (!isset($config['name'])) {
|
||||
@ -47,21 +53,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType
|
||||
|
||||
$this->name = $config['name'];
|
||||
$this->description = isset($config['description']) ? $config['description'] : null;
|
||||
$this->values = [];
|
||||
|
||||
if (!empty($config['values'])) {
|
||||
foreach ($config['values'] as $name => $value) {
|
||||
if (!is_array($value)) {
|
||||
if (is_string($name)) {
|
||||
$value = ['name' => $name, 'value' => $value];
|
||||
} else if (is_int($name) && is_string($value)) {
|
||||
$value = ['name' => $value, 'value' => $value];
|
||||
}
|
||||
}
|
||||
// value will be equal to name only if 'value' is not set in definition
|
||||
$this->values[] = new EnumValueDefinition($value + ['name' => $name, 'value' => $name]);
|
||||
}
|
||||
}
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,6 +61,33 @@ class EnumType extends Type implements InputType, OutputType, LeafType
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if ($this->values === null) {
|
||||
$this->values = [];
|
||||
$config = $this->config;
|
||||
|
||||
if (isset($config['values'])) {
|
||||
if (!is_array($config['values'])) {
|
||||
throw new InvariantViolation("{$this->name} values must be an array");
|
||||
}
|
||||
foreach ($config['values'] as $name => $value) {
|
||||
if (is_string($name)) {
|
||||
if (!is_array($value)) {
|
||||
throw new InvariantViolation(
|
||||
"{$this->name}.$name must refer to an associative array with a " .
|
||||
'"value" key representing an internal value but got: ' . Utils::printSafe($value)
|
||||
);
|
||||
}
|
||||
$value += ['name' => $name, 'value' => $name];
|
||||
} else if (is_int($name) && is_string($value)) {
|
||||
$value = ['name' => $value, 'value' => $value];
|
||||
} else {
|
||||
throw new InvariantViolation("{$this->name} values must be an array with value names as keys.");
|
||||
}
|
||||
$this->values[] = new EnumValueDefinition($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
@ -168,4 +187,42 @@ class EnumType extends Type implements InputType, OutputType, LeafType
|
||||
}
|
||||
return $this->nameLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
parent::assertValid();
|
||||
|
||||
Utils::invariant(
|
||||
isset($this->config['values']),
|
||||
"{$this->name} values must be an array."
|
||||
);
|
||||
|
||||
$values = $this->getValues();
|
||||
|
||||
Utils::invariant(
|
||||
!empty($values),
|
||||
"{$this->name} values must be not empty."
|
||||
);
|
||||
foreach ($values as $value) {
|
||||
try {
|
||||
Utils::assertValidName($value->name);
|
||||
} catch (InvariantViolation $e) {
|
||||
throw new InvariantViolation(
|
||||
"{$this->name} has value with invalid name: " .
|
||||
Utils::printSafe($value->name) . " ({$e->getMessage()})"
|
||||
);
|
||||
}
|
||||
Utils::invariant(
|
||||
!in_array($value->name, ['true', 'false', 'null']),
|
||||
"{$this->name}: \"{$value->name}\" can not be used as an Enum value."
|
||||
);
|
||||
Utils::invariant(
|
||||
!isset($value->config['isDeprecated']),
|
||||
"{$this->name}.{$value->name} should provide \"deprecationReason\" instead of \"isDeprecated\"."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,19 @@ class EnumValueDefinition
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $config;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
Utils::assign($this, $config);
|
||||
$this->name = isset($config['name']) ? $config['name'] : null;
|
||||
$this->value = isset($config['value']) ? $config['value'] : null;
|
||||
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
|
||||
$this->description = isset($config['description']) ? $config['description'] : null;
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
|
||||
/**
|
||||
* Class FieldArgument
|
||||
@ -105,4 +108,29 @@ class FieldArgument
|
||||
{
|
||||
return $this->defaultValueExists;
|
||||
}
|
||||
|
||||
public function assertValid(FieldDefinition $parentField, Type $parentType)
|
||||
{
|
||||
try {
|
||||
Utils::assertValidName($this->name);
|
||||
} catch (InvariantViolation $e) {
|
||||
throw new InvariantViolation(
|
||||
"{$parentType->name}.{$parentField->name}({$this->name}:) {$e->getMessage()}")
|
||||
;
|
||||
}
|
||||
$type = $this->type;
|
||||
if ($type instanceof WrappingType) {
|
||||
$type = $type->getWrappedType(true);
|
||||
}
|
||||
Utils::invariant(
|
||||
$type instanceof InputType,
|
||||
"{$parentType->name}.{$parentField->name}({$this->name}): argument type must be " .
|
||||
"Input Type but got: " . Utils::printSafe($this->type)
|
||||
);
|
||||
Utils::invariant(
|
||||
$this->description === null || is_string($this->description),
|
||||
"{$parentType->name}.{$parentField->name}({$this->name}): argument description type must be " .
|
||||
"string but got: " . Utils::printSafe($this->description)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -89,28 +89,72 @@ class FieldDefinition
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|Config $fields
|
||||
* @param string $parentTypeName
|
||||
* @return array
|
||||
*/
|
||||
public static function createMap(array $fields, $parentTypeName = null)
|
||||
public static function defineFieldMap(Type $type, $fields)
|
||||
{
|
||||
if (is_callable($fields)) {
|
||||
$fields = $fields();
|
||||
}
|
||||
if (!is_array($fields)) {
|
||||
throw new InvariantViolation(
|
||||
"{$type->name} fields must be an array or a callable which returns such an array."
|
||||
);
|
||||
}
|
||||
$map = [];
|
||||
foreach ($fields as $name => $field) {
|
||||
if (is_array($field)) {
|
||||
if (!isset($field['name']) && is_string($name)) {
|
||||
$field['name'] = $name;
|
||||
}
|
||||
$fieldDef = self::create($field, $parentTypeName);
|
||||
if (isset($field['args']) && !is_array($field['args'])) {
|
||||
throw new InvariantViolation(
|
||||
"{$type->name}.{$name} args must be an array."
|
||||
);
|
||||
}
|
||||
$fieldDef = self::create($field);
|
||||
} else if ($field instanceof FieldDefinition) {
|
||||
$fieldDef = $field;
|
||||
} else {
|
||||
if (is_string($name) && $field) {
|
||||
$fieldDef = self::create(['name' => $name, 'type' => $field]);
|
||||
} else {
|
||||
throw new InvariantViolation(
|
||||
"{$type->name}.$name field config must be an array, but got: " . Utils::printSafe($field)
|
||||
);
|
||||
}
|
||||
}
|
||||
$map[$fieldDef->name] = $fieldDef;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|Config $fields
|
||||
* @param string $parentTypeName
|
||||
* @deprecated
|
||||
* @return array
|
||||
*/
|
||||
public static function createMap(array $fields, $parentTypeName = null)
|
||||
{
|
||||
trigger_error(
|
||||
__METHOD__ . ' is deprecated, use ' . __CLASS__ . '::defineFieldMap() instead',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$map = [];
|
||||
foreach ($fields as $name => $field) {
|
||||
if (is_array($field)) {
|
||||
if (!isset($field['name']) && is_string($name)) {
|
||||
$field['name'] = $name;
|
||||
}
|
||||
$fieldDef = self::create($field);
|
||||
} else if ($field instanceof FieldDefinition) {
|
||||
$fieldDef = $field;
|
||||
} else {
|
||||
if (is_string($name)) {
|
||||
$fieldDef = self::create(['name' => $name, 'type' => $field], $parentTypeName);
|
||||
$fieldDef = self::create(['name' => $name, 'type' => $field]);
|
||||
} else {
|
||||
throw new InvariantViolation(
|
||||
"Unexpected field definition for type $parentTypeName at key $name: " . Utils::printSafe($field)
|
||||
"Unexpected field definition for type $parentTypeName at field $name: " . Utils::printSafe($field)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -195,6 +239,37 @@ class FieldDefinition
|
||||
return $this->complexityFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type $parentType
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid(Type $parentType)
|
||||
{
|
||||
try {
|
||||
Utils::assertValidName($this->name);
|
||||
} catch (InvariantViolation $e) {
|
||||
throw new InvariantViolation("{$parentType->name}.{$this->name}: {$e->getMessage()}");
|
||||
}
|
||||
Utils::invariant(
|
||||
!isset($this->config['isDeprecated']),
|
||||
"{$parentType->name}.{$this->name} should provide \"deprecationReason\" instead of \"isDeprecated\"."
|
||||
);
|
||||
|
||||
$type = $this->type;
|
||||
if ($type instanceof WrappingType) {
|
||||
$type = $type->getWrappedType(true);
|
||||
}
|
||||
Utils::invariant(
|
||||
$type instanceof OutputType,
|
||||
"{$parentType->name}.{$this->name} field type must be Output Type but got: " . Utils::printSafe($this->type)
|
||||
);
|
||||
Utils::invariant(
|
||||
$this->resolveFn === null || is_callable($this->resolveFn),
|
||||
"{$parentType->name}.{$this->name} field resolver must be a function if provided, but got: %s",
|
||||
Utils::printSafe($this->resolveFn)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $childrenComplexity
|
||||
* @return mixed
|
||||
|
@ -27,6 +27,11 @@ class InputObjectField
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* Helps to differentiate when `defaultValue` is `null` and when it was not even set initially
|
||||
*
|
||||
@ -52,6 +57,7 @@ class InputObjectField
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
$this->config = $opts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
@ -56,6 +57,13 @@ class InputObjectType extends Type implements InputType
|
||||
$this->fields = [];
|
||||
$fields = isset($this->config['fields']) ? $this->config['fields'] : [];
|
||||
$fields = is_callable($fields) ? call_user_func($fields) : $fields;
|
||||
|
||||
if (!is_array($fields)) {
|
||||
throw new InvariantViolation(
|
||||
"{$this->name} fields must be an array or a callable which returns such an array."
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($fields as $name => $field) {
|
||||
if ($field instanceof Type) {
|
||||
$field = ['type' => $field];
|
||||
@ -81,4 +89,41 @@ class InputObjectType extends Type implements InputType
|
||||
Utils::invariant(isset($this->fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);
|
||||
return $this->fields[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
parent::assertValid();
|
||||
|
||||
$fields = $this->getFields();
|
||||
|
||||
Utils::invariant(
|
||||
!empty($fields),
|
||||
"{$this->name} fields must not be empty"
|
||||
);
|
||||
|
||||
foreach ($fields as $field) {
|
||||
try {
|
||||
Utils::assertValidName($field->name);
|
||||
} catch (InvariantViolation $e) {
|
||||
throw new InvariantViolation("{$this->name}.{$field->name}: {$e->getMessage()}");
|
||||
}
|
||||
|
||||
$fieldType = $field->type;
|
||||
if ($fieldType instanceof WrappingType) {
|
||||
$fieldType = $fieldType->getWrappedType(true);
|
||||
}
|
||||
Utils::invariant(
|
||||
$fieldType instanceof InputType,
|
||||
"{$this->name}.{$field->name} field type must be Input Type but got: %s.",
|
||||
Utils::printSafe($field->type)
|
||||
);
|
||||
Utils::invariant(
|
||||
!isset($field->config['resolve']),
|
||||
"{$this->name}.{$field->name} field type has a resolve property, but Input Types cannot define resolvers."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
@ -57,10 +58,8 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
|
||||
public function getFields()
|
||||
{
|
||||
if (null === $this->fields) {
|
||||
$this->fields = [];
|
||||
$fields = isset($this->config['fields']) ? $this->config['fields'] : [];
|
||||
$fields = is_callable($fields) ? call_user_func($fields) : $fields;
|
||||
$this->fields = FieldDefinition::createMap($fields, $this->name);
|
||||
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
|
||||
}
|
||||
return $this->fields;
|
||||
}
|
||||
@ -95,4 +94,31 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
parent::assertValid();
|
||||
|
||||
$fields = $this->getFields();
|
||||
|
||||
Utils::invariant(
|
||||
!isset($this->config['resolveType']) || is_callable($this->config['resolveType']),
|
||||
"{$this->name} must provide \"resolveType\" as a function."
|
||||
);
|
||||
|
||||
Utils::invariant(
|
||||
!empty($fields),
|
||||
"{$this->name} fields must not be empty"
|
||||
);
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$field->assertValid($this);
|
||||
foreach ($field->args as $arg) {
|
||||
$arg->assertValid($field, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
@ -19,11 +20,11 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
|
||||
*/
|
||||
public function __construct($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
$type instanceof Type || is_callable($type),
|
||||
'Expecting instance of GraphQL\Type\Definition\Type or callable returning instance of that class'
|
||||
if (!$type instanceof Type && !is_callable($type)) {
|
||||
throw new InvariantViolation(
|
||||
'Can only create List of a GraphQLType but got: ' . Utils::printSafe($type)
|
||||
);
|
||||
|
||||
}
|
||||
$this->ofType = $type;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
@ -20,10 +21,16 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
*/
|
||||
public function __construct($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
$type instanceof Type || is_callable($type),
|
||||
'Expecting instance of GraphQL\Type\Definition\Type or callable returning instance of that class'
|
||||
if (!$type instanceof Type && !is_callable($type)) {
|
||||
throw new InvariantViolation(
|
||||
'Can only create NonNull of a Nullable GraphQLType but got: ' . Utils::printSafe($type)
|
||||
);
|
||||
}
|
||||
if ($type instanceof NonNull) {
|
||||
throw new InvariantViolation(
|
||||
'Can only create NonNull of a Nullable GraphQLType but got: ' . Utils::printSafe($type)
|
||||
);
|
||||
}
|
||||
Utils::invariant(
|
||||
!($type instanceof NonNull),
|
||||
'Cannot nest NonNull inside NonNull'
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
use GraphQL\Error\Error;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
@ -60,7 +60,7 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $interfaceMap = [];
|
||||
private $interfaceMap;
|
||||
|
||||
/**
|
||||
* Keeping reference of config for late bindings and custom app-level metadata
|
||||
@ -111,13 +111,13 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
|
||||
/**
|
||||
* @return FieldDefinition[]
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
if (null === $this->fields) {
|
||||
$fields = isset($this->config['fields']) ? $this->config['fields'] : [];
|
||||
$fields = is_callable($fields) ? call_user_func($fields) : $fields;
|
||||
$this->fields = FieldDefinition::createMap($fields, $this->name);
|
||||
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
|
||||
}
|
||||
return $this->fields;
|
||||
}
|
||||
@ -132,9 +132,7 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
if (null === $this->fields) {
|
||||
$this->getFields();
|
||||
}
|
||||
if (!isset($this->fields[$name])) {
|
||||
throw new Error(sprintf("Field '%s' is not defined for type '%s'", $name, $this->name));
|
||||
}
|
||||
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
|
||||
return $this->fields[$name];
|
||||
}
|
||||
|
||||
@ -147,11 +145,21 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
$interfaces = isset($this->config['interfaces']) ? $this->config['interfaces'] : [];
|
||||
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces;
|
||||
|
||||
if (!is_array($interfaces)) {
|
||||
throw new InvariantViolation(
|
||||
"{$this->name} interfaces must be an Array or a callable which returns an Array."
|
||||
);
|
||||
}
|
||||
|
||||
$this->interfaces = [];
|
||||
foreach ($interfaces as $iface) {
|
||||
$iface = Type::resolve($iface);
|
||||
if (!$iface instanceof InterfaceType) {
|
||||
throw new InvariantViolation(sprintf('Expecting interface type, got %s', Utils::printSafe($iface)));
|
||||
throw new InvariantViolation(sprintf(
|
||||
'%s may only implement Interface types, it cannot implement %s',
|
||||
$this->name,
|
||||
Utils::printSafe($iface)
|
||||
));
|
||||
}
|
||||
// TODO: return interfaceMap vs interfaces. Possibly breaking change?
|
||||
$this->interfaces[] = $iface;
|
||||
@ -161,6 +169,17 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
return $this->interfaces;
|
||||
}
|
||||
|
||||
private function getInterfaceMap()
|
||||
{
|
||||
if (!$this->interfaceMap) {
|
||||
$this->interfaceMap = [];
|
||||
foreach ($this->getInterfaces() as $interface) {
|
||||
$this->interfaceMap[$interface->name] = $interface;
|
||||
}
|
||||
}
|
||||
return $this->interfaceMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InterfaceType $iface
|
||||
* @return bool
|
||||
@ -182,4 +201,61 @@ class ObjectType extends Type implements OutputType, CompositeType
|
||||
{
|
||||
return isset($this->config['isTypeOf']) ? call_user_func($this->config['isTypeOf'], $value, $context, $info) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates type config and throws if one of type options is invalid.
|
||||
* Note: this method is shallow, it won't validate object fields and their arguments.
|
||||
*
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
parent::assertValid();
|
||||
|
||||
Utils::invariant(
|
||||
null === $this->description || is_string($this->description),
|
||||
"{$this->name} description must be string if set, but it is: " . Utils::printSafe($this->description)
|
||||
);
|
||||
|
||||
Utils::invariant(
|
||||
!isset($this->config['isTypeOf']) || is_callable($this->config['isTypeOf']),
|
||||
"{$this->name} must provide 'isTypeOf' as a function"
|
||||
);
|
||||
|
||||
// getFields() and getInterfaceMap() will do structural validation
|
||||
$fields = $this->getFields();
|
||||
Utils::invariant(
|
||||
!empty($fields),
|
||||
"{$this->name} fields must not be empty"
|
||||
);
|
||||
foreach ($fields as $field) {
|
||||
$field->assertValid($this);
|
||||
foreach ($field->args as $arg) {
|
||||
$arg->assertValid($field, $this);
|
||||
}
|
||||
}
|
||||
|
||||
$implemented = [];
|
||||
foreach ($this->getInterfaces() as $iface) {
|
||||
Utils::invariant(
|
||||
$iface instanceof InterfaceType,
|
||||
"{$this->name} may only implement Interface types, it cannot implement: %s.",
|
||||
Utils::printSafe($iface)
|
||||
);
|
||||
Utils::invariant(
|
||||
!isset($implemented[$iface->name]),
|
||||
"{$this->name} may declare it implements {$iface->name} only once."
|
||||
);
|
||||
$implemented[$iface->name] = true;
|
||||
if (!isset($iface->config['resolveType'])) {
|
||||
Utils::invariant(
|
||||
isset($this->config['isTypeOf']),
|
||||
"Interface Type {$iface->name} does not provide a \"resolveType\" " .
|
||||
"function and implementing Type {$this->name} does not provide a " .
|
||||
'"isTypeOf" function. There is no way to resolve this implementing ' .
|
||||
'type during execution.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +237,13 @@ abstract class Type implements \JsonSerializable
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
@ -68,17 +69,19 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
||||
public function getTypes()
|
||||
{
|
||||
if (null === $this->types) {
|
||||
if ($this->config['types'] instanceof \Closure) {
|
||||
if (!isset($this->config['types'])) {
|
||||
$types = null;
|
||||
} else if (is_callable($this->config['types'])) {
|
||||
$types = call_user_func($this->config['types']);
|
||||
} else {
|
||||
$types = $this->config['types'];
|
||||
}
|
||||
|
||||
Utils::invariant(
|
||||
is_array($types),
|
||||
'Option "types" of union "%s" is expected to return array of types (or closure returning array of types)',
|
||||
$this->name
|
||||
if (!is_array($types)) {
|
||||
throw new InvariantViolation(
|
||||
"{$this->name} types must be an Array or a callable which returns an Array."
|
||||
);
|
||||
}
|
||||
|
||||
$this->types = [];
|
||||
foreach ($types as $type) {
|
||||
@ -123,4 +126,48 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
parent::assertValid();
|
||||
|
||||
$types = $this->getTypes();
|
||||
Utils::invariant(
|
||||
!empty($types),
|
||||
"{$this->name} types must not be empty"
|
||||
);
|
||||
|
||||
if (isset($this->config['resolveType'])) {
|
||||
Utils::invariant(
|
||||
is_callable($this->config['resolveType']),
|
||||
"{$this->name} must provide \"resolveType\" as a function."
|
||||
);
|
||||
}
|
||||
|
||||
$includedTypeNames = [];
|
||||
foreach ($types as $objType) {
|
||||
Utils::invariant(
|
||||
$objType instanceof ObjectType,
|
||||
"{$this->name} may only contain Object types, it cannot contain: %s.",
|
||||
Utils::printSafe($objType)
|
||||
);
|
||||
Utils::invariant(
|
||||
!isset($includedTypeNames[$objType->name]),
|
||||
"{$this->name} can include {$objType->name} type only once."
|
||||
);
|
||||
$includedTypeNames[$objType->name] = true;
|
||||
if (!isset($this->config['resolveType'])) {
|
||||
Utils::invariant(
|
||||
isset($objType->config['isTypeOf']) && is_callable($objType->config['isTypeOf']),
|
||||
"Union type \"{$this->name}\" does not provide a \"resolveType\" " .
|
||||
"function and possible type \"{$objType->name}\" does not provide an " .
|
||||
'"isTypeOf" function. There is no way to resolve this possible type ' .
|
||||
'during execution.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -390,4 +390,111 @@ class Schema
|
||||
}
|
||||
return $this->resolvedTypes[$typeName];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
foreach ($this->config->getDirectives() as $index => $directive) {
|
||||
Utils::invariant(
|
||||
$directive instanceof Directive,
|
||||
"Each entry of \"directives\" option of Schema config must be an instance of %s but entry at position %d is %s.",
|
||||
Directive::class,
|
||||
$index,
|
||||
Utils::printSafe($directive)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->getTypeMap() as $name => $type) {
|
||||
$type->assertValid();
|
||||
|
||||
if ($type instanceof AbstractType) {
|
||||
$possibleTypes = $this->getPossibleTypes($type);
|
||||
|
||||
Utils::invariant(
|
||||
!empty($possibleTypes),
|
||||
"Could not find possible implementing types for {$type->name} " .
|
||||
'in schema. Check that schema.types is defined and is an array of ' .
|
||||
'all possible types in the schema.'
|
||||
);
|
||||
|
||||
} else if ($type instanceof ObjectType) {
|
||||
foreach ($type->getInterfaces() as $iface) {
|
||||
$this->assertImplementsIntarface($type, $iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function assertImplementsIntarface(ObjectType $object, InterfaceType $iface)
|
||||
{
|
||||
$objectFieldMap = $object->getFields();
|
||||
$ifaceFieldMap = $iface->getFields();
|
||||
|
||||
// Assert each interface field is implemented.
|
||||
foreach ($ifaceFieldMap as $fieldName => $ifaceField) {
|
||||
|
||||
// Assert interface field exists on object.
|
||||
Utils::invariant(
|
||||
isset($objectFieldMap[$fieldName]),
|
||||
"{$iface->name} expects field \"{$fieldName}\" but {$object->name} does not provide it"
|
||||
);
|
||||
|
||||
$objectField = $objectFieldMap[$fieldName];
|
||||
|
||||
// Assert interface field type is satisfied by object field type, by being
|
||||
// a valid subtype. (covariant)
|
||||
Utils::invariant(
|
||||
TypeComparators::isTypeSubTypeOf($this, $objectField->getType(), $ifaceField->getType()),
|
||||
"{$iface->name}.{$fieldName} expects type \"{$ifaceField->getType()}\" " .
|
||||
"but " .
|
||||
"{$object->name}.${fieldName} provides type \"{$objectField->getType()}\""
|
||||
);
|
||||
|
||||
// Assert each interface field arg is implemented.
|
||||
foreach ($ifaceField->args as $ifaceArg) {
|
||||
$argName = $ifaceArg->name;
|
||||
|
||||
/** @var FieldArgument $objectArg */
|
||||
$objectArg = Utils::find($objectField->args, function(FieldArgument $arg) use ($argName) {
|
||||
return $arg->name === $argName;
|
||||
});
|
||||
|
||||
// Assert interface field arg exists on object field.
|
||||
Utils::invariant(
|
||||
$objectArg,
|
||||
"{$iface->name}.{$fieldName} expects argument \"{$argName}\" but ".
|
||||
"{$object->name}.{$fieldName} does not provide it."
|
||||
);
|
||||
|
||||
// Assert interface field arg type matches object field arg type.
|
||||
// (invariant)
|
||||
Utils::invariant(
|
||||
TypeComparators::isEqualType($ifaceArg->getType(), $objectArg->getType()),
|
||||
"{$iface->name}.{$fieldName}({$argName}:) expects type " .
|
||||
"\"{$ifaceArg->getType()->name}\" but " .
|
||||
"{$object->name}.{$fieldName}({$argName}:) provides type " .
|
||||
"\"{$objectArg->getType()->name}\"."
|
||||
);
|
||||
|
||||
// Assert additional arguments must not be required.
|
||||
foreach ($objectField->args as $objectArg) {
|
||||
$argName = $objectArg->name;
|
||||
$ifaceArg = Utils::find($ifaceField->args, function(FieldArgument $arg) use ($argName) {
|
||||
return $arg->name === $argName;
|
||||
});
|
||||
if (!$ifaceArg) {
|
||||
Utils::invariant(
|
||||
!($objectArg->getType() instanceof NonNull),
|
||||
"{$object->name}.{$fieldName}({$argName}:) is of required type " .
|
||||
"\"{$objectArg->getType()}\" but is not also provided by the " .
|
||||
"interface {$iface->name}.{$fieldName}."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Type;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Type\Descriptor;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
@ -203,7 +204,7 @@ class SchemaConfig
|
||||
*/
|
||||
public function getDirectives()
|
||||
{
|
||||
return $this->directives;
|
||||
return $this->directives ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
137
src/Utils/TypeComparators.php
Normal file
137
src/Utils/TypeComparators.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
namespace GraphQL\Utils;
|
||||
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\AbstractType;
|
||||
use GraphQL\Type\Definition\CompositeType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class TypeComparators
|
||||
{
|
||||
/**
|
||||
* Provided two types, return true if the types are equal (invariant).
|
||||
*
|
||||
* @param Type $typeA
|
||||
* @param Type $typeB
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEqualType(Type $typeA, Type $typeB)
|
||||
{
|
||||
// Equivalent types are equal.
|
||||
if ($typeA === $typeB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If either type is non-null, the other must also be non-null.
|
||||
if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
|
||||
return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
|
||||
// If either type is a list, the other must also be a list.
|
||||
if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
|
||||
return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
|
||||
// Otherwise the types are not equal.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided a type and a super type, return true if the first type is either
|
||||
* equal or a subset of the second super type (covariant).
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param Type $maybeSubType
|
||||
* @param Type $superType
|
||||
* @return bool
|
||||
*/
|
||||
static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
|
||||
{
|
||||
// Equivalent type is a valid subtype
|
||||
if ($maybeSubType === $superType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If superType is non-null, maybeSubType must also be nullable.
|
||||
if ($superType instanceof NonNull) {
|
||||
if ($maybeSubType instanceof NonNull) {
|
||||
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
|
||||
}
|
||||
return false;
|
||||
} else if ($maybeSubType instanceof NonNull) {
|
||||
// If superType is nullable, maybeSubType may be non-null.
|
||||
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType);
|
||||
}
|
||||
|
||||
// If superType type is a list, maybeSubType type must also be a list.
|
||||
if ($superType instanceof ListOfType) {
|
||||
if ($maybeSubType instanceof ListOfType) {
|
||||
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
|
||||
}
|
||||
return false;
|
||||
} else if ($maybeSubType instanceof ListOfType) {
|
||||
// If superType is not a list, maybeSubType must also be not a list.
|
||||
return false;
|
||||
}
|
||||
|
||||
// If superType type is an abstract type, maybeSubType type may be a currently
|
||||
// possible object type.
|
||||
if (Type::isAbstractType($superType) && $maybeSubType instanceof ObjectType && $schema->isPossibleType($superType, $maybeSubType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, the child type is not a valid subtype of the parent type.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided two composite types, determine if they "overlap". Two composite
|
||||
* types overlap when the Sets of possible concrete types for each intersect.
|
||||
*
|
||||
* This is often used to determine if a fragment of a given type could possibly
|
||||
* be visited in a context of another type.
|
||||
*
|
||||
* This function is commutative.
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param CompositeType $typeA
|
||||
* @param CompositeType $typeB
|
||||
* @return bool
|
||||
*/
|
||||
static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
|
||||
{
|
||||
// Equivalent types overlap
|
||||
if ($typeA === $typeB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($typeA instanceof AbstractType) {
|
||||
if ($typeB instanceof AbstractType) {
|
||||
// If both types are abstract, then determine if there is any intersection
|
||||
// between possible concrete types of each.
|
||||
foreach ($schema->getPossibleTypes($typeA) as $type) {
|
||||
if ($schema->isPossibleType($typeB, $type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var $typeB ObjectType */
|
||||
// Determine if the latter type is a possible concrete type of the former.
|
||||
return $schema->isPossibleType($typeA, $typeB);
|
||||
}
|
||||
|
||||
if ($typeB instanceof AbstractType) {
|
||||
/** @var $typeA ObjectType */
|
||||
// Determine if the former type is a possible concrete type of the latter.
|
||||
return $schema->isPossibleType($typeB, $typeA);
|
||||
}
|
||||
|
||||
// Otherwise the types do not overlap.
|
||||
return false;
|
||||
}
|
||||
}
|
@ -7,8 +7,7 @@ use GraphQL\Language\AST\NamedTypeNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NonNullTypeNode;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\AbstractType;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\CompositeType;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
@ -32,114 +31,27 @@ use GraphQL\Type\Introspection;
|
||||
class TypeInfo
|
||||
{
|
||||
/**
|
||||
* Provided two types, return true if the types are equal (invariant).
|
||||
* @deprecated moved to GraphQL\Utils\TypeComparators
|
||||
*/
|
||||
public static function isEqualType(Type $typeA, Type $typeB)
|
||||
{
|
||||
// Equivalent types are equal.
|
||||
if ($typeA === $typeB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If either type is non-null, the other must also be non-null.
|
||||
if ($typeA instanceof NonNull && $typeB instanceof NonNull) {
|
||||
return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
|
||||
// If either type is a list, the other must also be a list.
|
||||
if ($typeA instanceof ListOfType && $typeB instanceof ListOfType) {
|
||||
return self::isEqualType($typeA->getWrappedType(), $typeB->getWrappedType());
|
||||
}
|
||||
|
||||
// Otherwise the types are not equal.
|
||||
return false;
|
||||
return TypeComparators::isEqualType($typeA, $typeB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided a type and a super type, return true if the first type is either
|
||||
* equal or a subset of the second super type (covariant).
|
||||
* @deprecated moved to GraphQL\Utils\TypeComparators
|
||||
*/
|
||||
static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
|
||||
{
|
||||
// Equivalent type is a valid subtype
|
||||
if ($maybeSubType === $superType) {
|
||||
return true;
|
||||
return TypeComparators::isTypeSubTypeOf($schema, $maybeSubType, $superType);
|
||||
}
|
||||
|
||||
// If superType is non-null, maybeSubType must also be nullable.
|
||||
if ($superType instanceof NonNull) {
|
||||
if ($maybeSubType instanceof NonNull) {
|
||||
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
|
||||
}
|
||||
return false;
|
||||
} else if ($maybeSubType instanceof NonNull) {
|
||||
// If superType is nullable, maybeSubType may be non-null.
|
||||
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType);
|
||||
}
|
||||
|
||||
// If superType type is a list, maybeSubType type must also be a list.
|
||||
if ($superType instanceof ListOfType) {
|
||||
if ($maybeSubType instanceof ListOfType) {
|
||||
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
|
||||
}
|
||||
return false;
|
||||
} else if ($maybeSubType instanceof ListOfType) {
|
||||
// If superType is not a list, maybeSubType must also be not a list.
|
||||
return false;
|
||||
}
|
||||
|
||||
// If superType type is an abstract type, maybeSubType type may be a currently
|
||||
// possible object type.
|
||||
if (Type::isAbstractType($superType) && $maybeSubType instanceof ObjectType && $schema->isPossibleType($superType, $maybeSubType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, the child type is not a valid subtype of the parent type.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provided two composite types, determine if they "overlap". Two composite
|
||||
* types overlap when the Sets of possible concrete types for each intersect.
|
||||
*
|
||||
* This is often used to determine if a fragment of a given type could possibly
|
||||
* be visited in a context of another type.
|
||||
*
|
||||
* This function is commutative.
|
||||
* @deprecated moved to GraphQL\Utils\TypeComparators
|
||||
*/
|
||||
static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
|
||||
{
|
||||
// Equivalent types overlap
|
||||
if ($typeA === $typeB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($typeA instanceof AbstractType) {
|
||||
if ($typeB instanceof AbstractType) {
|
||||
// If both types are abstract, then determine if there is any intersection
|
||||
// between possible concrete types of each.
|
||||
foreach ($schema->getPossibleTypes($typeA) as $type) {
|
||||
if ($schema->isPossibleType($typeB, $type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var $typeB ObjectType */
|
||||
// Determine if the latter type is a possible concrete type of the former.
|
||||
return $schema->isPossibleType($typeA, $typeB);
|
||||
}
|
||||
|
||||
if ($typeB instanceof AbstractType) {
|
||||
/** @var $typeA ObjectType */
|
||||
// Determine if the former type is a possible concrete type of the latter.
|
||||
return $schema->isPossibleType($typeB, $typeA);
|
||||
}
|
||||
|
||||
// Otherwise the types do not overlap.
|
||||
return false;
|
||||
return TypeComparators::doTypesOverlap($schema, $typeA, $typeB);
|
||||
}
|
||||
|
||||
|
||||
|
@ -218,7 +218,7 @@ class Utils
|
||||
* @param string $message
|
||||
* @param mixed $sprintfParam1
|
||||
* @param mixed $sprintfParam2 ...
|
||||
* @throws \Exception
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public static function invariant($test, $message = '')
|
||||
{
|
||||
@ -420,7 +420,7 @@ class Utils
|
||||
/**
|
||||
* @param $name
|
||||
* @param bool $isIntrospection
|
||||
* @throws Error
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public static function assertValidName($name, $isIntrospection = false)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Utils\TypeComparators;
|
||||
use GraphQL\Utils\TypeInfo;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
|
||||
@ -45,7 +46,7 @@ class VariablesInAllowedPosition
|
||||
$schema = $context->getSchema();
|
||||
$varType = TypeInfo::typeFromAST($schema, $varDef->type);
|
||||
|
||||
if ($varType && !TypeInfo::isTypeSubTypeOf($schema, $this->effectiveType($varType, $varDef), $type)) {
|
||||
if ($varType && !TypeComparators::isTypeSubTypeOf($schema, $this->effectiveType($varType, $varDef), $type)) {
|
||||
$context->reportError(new Error(
|
||||
self::badVarPosMessage($varName, $varType, $type),
|
||||
[$varDef, $node]
|
||||
|
@ -62,12 +62,12 @@ class LazyInterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
if (!$this->lazyInterface) {
|
||||
$this->lazyInterface = new InterfaceType([
|
||||
'name' => 'LazyInterface',
|
||||
'fields' => [
|
||||
'a' => Type::string()
|
||||
],
|
||||
'resolveType' => function() {
|
||||
return $this->getTestObjectType();
|
||||
},
|
||||
'resolve' => function() {
|
||||
return [];
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class ServerTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testSchemaDefinition()
|
||||
{
|
||||
$mutationType = $queryType = $subscriptionType = new ObjectType(['name' => 'A', 'fields' => []]);
|
||||
$mutationType = $queryType = $subscriptionType = new ObjectType(['name' => 'A', 'fields' => ['a' => Type::string()]]);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $queryType
|
||||
@ -283,7 +283,7 @@ class ServerTest extends \PHPUnit_Framework_TestCase
|
||||
public function testValidate()
|
||||
{
|
||||
$server = Server::create()
|
||||
->setQueryType(new ObjectType(['name' => 'Q', 'fields' => []]));
|
||||
->setQueryType(new ObjectType(['name' => 'Q', 'fields' => ['a' => Type::string()]]));
|
||||
|
||||
$ast = $server->parse('{q}');
|
||||
$errors = $server->validate($ast);
|
||||
|
@ -243,11 +243,11 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$value = $enumTypeWithDeprecatedValue->getValues()[0];
|
||||
|
||||
$this->assertEquals([
|
||||
$this->assertArraySubset([
|
||||
'name' => 'foo',
|
||||
'description' => null,
|
||||
'deprecationReason' => 'Just because',
|
||||
'value' => 'foo'
|
||||
'value' => 'foo',
|
||||
], (array) $value);
|
||||
|
||||
$this->assertEquals(true, $value->isDeprecated());
|
||||
@ -284,8 +284,8 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
$actual = $EnumTypeWithNullishValue->getValues();
|
||||
|
||||
$this->assertEquals(count($expected), count($actual));
|
||||
$this->assertEquals($expected[0], (array)$actual[0]);
|
||||
$this->assertEquals($expected[1], (array)$actual[1]);
|
||||
$this->assertArraySubset($expected[0], (array)$actual[0]);
|
||||
$this->assertArraySubset($expected[1], (array)$actual[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
$emptySchema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'QueryRoot',
|
||||
'fields' => []
|
||||
'fields' => ['a' => Type::string()]
|
||||
])
|
||||
]);
|
||||
|
||||
@ -42,7 +42,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
'types' =>
|
||||
array (
|
||||
0 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => 'QueryRoot',
|
||||
@ -52,9 +51,29 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
'fields' => Array ()
|
||||
'fields' => array (
|
||||
array (
|
||||
'name' => 'a',
|
||||
'args' => array(),
|
||||
'type' => array(
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'String',
|
||||
'ofType' => null
|
||||
),
|
||||
'isDeprecated' => false,
|
||||
'deprecationReason' => null,
|
||||
)
|
||||
)
|
||||
),
|
||||
array (
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'String',
|
||||
'fields' => NULL,
|
||||
'inputFields' => NULL,
|
||||
'interfaces' => NULL,
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => '__Schema',
|
||||
@ -108,7 +127,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'isDeprecated' => false,
|
||||
'deprecationReason' => NULL,
|
||||
),
|
||||
2 =>
|
||||
array (
|
||||
'name' => 'mutationType',
|
||||
'args' =>
|
||||
@ -122,7 +140,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'isDeprecated' => false,
|
||||
'deprecationReason' => NULL,
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
'name' => 'subscriptionType',
|
||||
'args' =>
|
||||
@ -136,7 +153,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'isDeprecated' => false,
|
||||
'deprecationReason' => NULL,
|
||||
),
|
||||
4 =>
|
||||
array (
|
||||
'name' => 'directives',
|
||||
'args' =>
|
||||
@ -173,7 +189,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
2 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => '__Type',
|
||||
@ -388,7 +403,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
'kind' => 'ENUM',
|
||||
'name' => '__TypeKind',
|
||||
@ -448,17 +462,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
4 =>
|
||||
array (
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'String',
|
||||
'fields' => NULL,
|
||||
'inputFields' => NULL,
|
||||
'interfaces' => NULL,
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
5 =>
|
||||
array (
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'Boolean',
|
||||
@ -468,7 +471,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
6 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => '__Field',
|
||||
@ -596,7 +598,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
7 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => '__InputValue',
|
||||
@ -676,7 +677,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
8 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => '__EnumValue',
|
||||
@ -756,7 +756,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
9 =>
|
||||
array (
|
||||
'kind' => 'OBJECT',
|
||||
'name' => '__Directive',
|
||||
@ -919,7 +918,6 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
10 =>
|
||||
array (
|
||||
'kind' => 'ENUM',
|
||||
'name' => '__DirectiveLocation',
|
||||
@ -973,7 +971,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
11 => array (
|
||||
array (
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'ID',
|
||||
'fields' => NULL,
|
||||
@ -982,7 +980,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
12 => array (
|
||||
array (
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'Float',
|
||||
'fields' => NULL,
|
||||
@ -991,7 +989,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
'enumValues' => NULL,
|
||||
'possibleTypes' => NULL,
|
||||
),
|
||||
13 => array (
|
||||
array (
|
||||
'kind' => 'SCALAR',
|
||||
'name' => 'Int',
|
||||
'fields' => NULL,
|
||||
@ -1491,7 +1489,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$QueryRoot = new ObjectType([
|
||||
'name' => 'QueryRoot',
|
||||
'fields' => []
|
||||
'fields' => ['a' => Type::string()]
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $QueryRoot]);
|
||||
@ -1551,7 +1549,7 @@ class IntrospectionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$QueryRoot = new ObjectType([
|
||||
'name' => 'QueryRoot',
|
||||
'fields' => []
|
||||
'fields' => ['a' => Type::string()]
|
||||
]);
|
||||
|
||||
$schema = new Schema(['query' => $QueryRoot]);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -318,7 +318,7 @@ class ExtractTypesTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$otherUserType = new ObjectType([
|
||||
'name' => 'User',
|
||||
'fields' => []
|
||||
'fields' => ['a' => Type::string()]
|
||||
]);
|
||||
|
||||
$queryType = new ObjectType([
|
||||
|
Loading…
Reference in New Issue
Block a user