mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-24 22:06:04 +03:00
Better Predicates
Introduces new assertion functions for each kind of type mirroring the existing ones for the higher order types. ref: graphql/graphql-js#1137
This commit is contained in:
parent
60df83f47e
commit
9387548aa1
@ -2115,7 +2115,7 @@ static function valueFromASTUntyped($valueNode, array $variables = null)
|
||||
* @param Schema $schema
|
||||
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
||||
* @return Type
|
||||
* @throws InvariantViolation
|
||||
* @throws \Exception
|
||||
*/
|
||||
static function typeFromAST(GraphQL\Type\Schema $schema, $inputTypeNode)
|
||||
```
|
||||
|
@ -12,6 +12,20 @@ use GraphQL\Utils\Utils;
|
||||
*/
|
||||
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType
|
||||
{
|
||||
/**
|
||||
* @param mixed $type
|
||||
* @return self
|
||||
*/
|
||||
public static function assertInterfaceType($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
$type instanceof self,
|
||||
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL Interface type.'
|
||||
);
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var FieldDefinition[]
|
||||
*/
|
||||
|
@ -20,12 +20,7 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
|
||||
*/
|
||||
public function __construct($type)
|
||||
{
|
||||
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;
|
||||
$this->ofType = Type::assertType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,35 @@ use GraphQL\Utils\Utils;
|
||||
class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
{
|
||||
/**
|
||||
* @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
|
||||
* @param mixed $type
|
||||
* @return self
|
||||
*/
|
||||
public static function assertNullType($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
$type instanceof self,
|
||||
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL Non-Null type.'
|
||||
);
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $type
|
||||
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
|
||||
*/
|
||||
public static function assertNullableType($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
Type::isType($type) && !$type instanceof self,
|
||||
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL nullable type.'
|
||||
);
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
|
||||
*/
|
||||
private $ofType;
|
||||
|
||||
@ -21,37 +49,17 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
*/
|
||||
public function __construct($type)
|
||||
{
|
||||
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'
|
||||
);
|
||||
$this->ofType = $type;
|
||||
$this->ofType = self::assertNullableType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $recurse
|
||||
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
|
||||
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
|
||||
* @throws InvariantViolation
|
||||
*/
|
||||
public function getWrappedType($recurse = false)
|
||||
{
|
||||
$type = $this->ofType;
|
||||
|
||||
Utils::invariant(
|
||||
!($type instanceof NonNull),
|
||||
'Cannot nest NonNull inside NonNull'
|
||||
);
|
||||
|
||||
return ($recurse && $type instanceof WrappingType) ? $type->getWrappedType($recurse) : $type;
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,20 @@ use GraphQL\Utils\Utils;
|
||||
*/
|
||||
class ObjectType extends Type implements OutputType, CompositeType
|
||||
{
|
||||
/**
|
||||
* @param mixed $type
|
||||
* @return self
|
||||
*/
|
||||
public static function assertObjectType($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
$type instanceof self,
|
||||
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL Object type.'
|
||||
);
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var FieldDefinition[]
|
||||
*/
|
||||
|
@ -4,8 +4,10 @@ namespace GraphQL\Type\Definition;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Language\AST\ListType;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\NonNullType;
|
||||
use GraphQL\Language\AST\TypeDefinitionNode;
|
||||
use GraphQL\Type\Introspection;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
* Registry of standard GraphQL types
|
||||
@ -218,11 +220,25 @@ abstract class Type implements \JsonSerializable
|
||||
$type instanceof UnionType ||
|
||||
$type instanceof EnumType ||
|
||||
$type instanceof InputObjectType ||
|
||||
$type instanceof ListType ||
|
||||
$type instanceof ListOfType ||
|
||||
$type instanceof NonNull
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $type
|
||||
* @return mixed
|
||||
*/
|
||||
public static function assertType($type)
|
||||
{
|
||||
Utils::invariant(
|
||||
self::isType($type),
|
||||
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL type.'
|
||||
);
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param Type $type
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Utils;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Language\AST\BooleanValueNode;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
@ -205,51 +206,50 @@ class AST
|
||||
return new ObjectValueNode(['fields' => $fieldNodes]);
|
||||
}
|
||||
|
||||
Utils::invariant(
|
||||
$type instanceof ScalarType || $type instanceof EnumType,
|
||||
"Must provide Input Type, cannot use: " . Utils::printSafe($type)
|
||||
);
|
||||
if ($type instanceof ScalarType || $type instanceof EnumType) {
|
||||
// Since value is an internally represented value, it must be serialized
|
||||
// to an externally represented value before converting into an AST.
|
||||
$serialized = $type->serialize($value);
|
||||
if (null === $serialized || Utils::isInvalid($serialized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Since value is an internally represented value, it must be serialized
|
||||
// to an externally represented value before converting into an AST.
|
||||
$serialized = $type->serialize($value);
|
||||
if (null === $serialized || Utils::isInvalid($serialized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Others serialize based on their corresponding PHP scalar types.
|
||||
if (is_bool($serialized)) {
|
||||
return new BooleanValueNode(['value' => $serialized]);
|
||||
}
|
||||
if (is_int($serialized)) {
|
||||
return new IntValueNode(['value' => $serialized]);
|
||||
}
|
||||
if (is_float($serialized)) {
|
||||
if ((int) $serialized == $serialized) {
|
||||
// Others serialize based on their corresponding PHP scalar types.
|
||||
if (is_bool($serialized)) {
|
||||
return new BooleanValueNode(['value' => $serialized]);
|
||||
}
|
||||
if (is_int($serialized)) {
|
||||
return new IntValueNode(['value' => $serialized]);
|
||||
}
|
||||
return new FloatValueNode(['value' => $serialized]);
|
||||
}
|
||||
if (is_string($serialized)) {
|
||||
// Enum types use Enum literals.
|
||||
if ($type instanceof EnumType) {
|
||||
return new EnumValueNode(['value' => $serialized]);
|
||||
if (is_float($serialized)) {
|
||||
if ((int) $serialized == $serialized) {
|
||||
return new IntValueNode(['value' => $serialized]);
|
||||
}
|
||||
return new FloatValueNode(['value' => $serialized]);
|
||||
}
|
||||
if (is_string($serialized)) {
|
||||
// Enum types use Enum literals.
|
||||
if ($type instanceof EnumType) {
|
||||
return new EnumValueNode(['value' => $serialized]);
|
||||
}
|
||||
|
||||
// ID types can use Int literals.
|
||||
$asInt = (int) $serialized;
|
||||
if ($type instanceof IDType && (string) $asInt === $serialized) {
|
||||
return new IntValueNode(['value' => $serialized]);
|
||||
}
|
||||
|
||||
// Use json_encode, which uses the same string encoding as GraphQL,
|
||||
// then remove the quotes.
|
||||
return new StringValueNode([
|
||||
'value' => substr(json_encode($serialized), 1, -1)
|
||||
]);
|
||||
}
|
||||
|
||||
// ID types can use Int literals.
|
||||
$asInt = (int) $serialized;
|
||||
if ($type instanceof IDType && (string) $asInt === $serialized) {
|
||||
return new IntValueNode(['value' => $serialized]);
|
||||
}
|
||||
|
||||
// Use json_encode, which uses the same string encoding as GraphQL,
|
||||
// then remove the quotes.
|
||||
return new StringValueNode([
|
||||
'value' => substr(json_encode($serialized), 1, -1)
|
||||
]);
|
||||
throw new InvariantViolation('Cannot convert value to AST: ' . Utils::printSafe($serialized));
|
||||
}
|
||||
|
||||
throw new InvariantViolation('Cannot convert value to AST: ' . Utils::printSafe($serialized));
|
||||
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -395,25 +395,26 @@ class AST
|
||||
return $enumValue->value;
|
||||
}
|
||||
|
||||
Utils::invariant($type instanceof ScalarType, 'Must be scalar type');
|
||||
/** @var ScalarType $type */
|
||||
if ($type instanceof ScalarType) {
|
||||
// Scalars fulfill parsing a literal value via parseLiteral().
|
||||
// Invalid values represent a failure to parse correctly, in which case
|
||||
// no value is returned.
|
||||
try {
|
||||
$result = $type->parseLiteral($valueNode, $variables);
|
||||
} catch (\Exception $error) {
|
||||
return $undefined;
|
||||
} catch (\Throwable $error) {
|
||||
return $undefined;
|
||||
}
|
||||
|
||||
// Scalars fulfill parsing a literal value via parseLiteral().
|
||||
// Invalid values represent a failure to parse correctly, in which case
|
||||
// no value is returned.
|
||||
try {
|
||||
$result = $type->parseLiteral($valueNode, $variables);
|
||||
} catch (\Exception $error) {
|
||||
return $undefined;
|
||||
} catch (\Throwable $error) {
|
||||
return $undefined;
|
||||
if (Utils::isInvalid($result)) {
|
||||
return $undefined;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (Utils::isInvalid($result)) {
|
||||
return $undefined;
|
||||
}
|
||||
|
||||
return $result;
|
||||
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -473,9 +474,9 @@ class AST
|
||||
return ($variables && isset($variables[$variableName]) && !Utils::isInvalid($variables[$variableName]))
|
||||
? $variables[$variableName]
|
||||
: null;
|
||||
default:
|
||||
throw new InvariantViolation('Unexpected value kind: ' . $valueNode->kind);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unexpected value kind: ' . $valueNode->kind . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -485,7 +486,7 @@ class AST
|
||||
* @param Schema $schema
|
||||
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
||||
* @return Type
|
||||
* @throws InvariantViolation
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function typeFromAST(Schema $schema, $inputTypeNode)
|
||||
{
|
||||
@ -497,9 +498,11 @@ class AST
|
||||
$innerType = self::typeFromAST($schema, $inputTypeNode->type);
|
||||
return $innerType ? new NonNull($innerType) : null;
|
||||
}
|
||||
if ($inputTypeNode instanceof NamedTypeNode) {
|
||||
return $schema->getType($inputTypeNode->name->value);
|
||||
}
|
||||
|
||||
Utils::invariant($inputTypeNode && $inputTypeNode instanceof NamedTypeNode, 'Must be a named type');
|
||||
return $schema->getType($inputTypeNode->name->value);
|
||||
throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,8 +75,7 @@ class ASTDefinitionBuilder
|
||||
}
|
||||
if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) {
|
||||
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
|
||||
Utils::invariant(!($wrappedType instanceof NonNull), 'No nesting nonnull.');
|
||||
return Type::nonNull($wrappedType);
|
||||
return Type::nonNull(NonNull::assertNullableType($wrappedType));
|
||||
}
|
||||
return $innerType;
|
||||
}
|
||||
@ -159,8 +158,7 @@ class ASTDefinitionBuilder
|
||||
public function buildObjectType($typeNode)
|
||||
{
|
||||
$type = $this->buildType($typeNode);
|
||||
Utils::invariant($type instanceof ObjectType, 'Expected Object type.' . get_class($type));
|
||||
return $type;
|
||||
return ObjectType::assertObjectType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,8 +169,7 @@ class ASTDefinitionBuilder
|
||||
public function buildInterfaceType($typeNode)
|
||||
{
|
||||
$type = $this->buildType($typeNode);
|
||||
Utils::invariant($type instanceof InterfaceType, 'Expected Interface type.');
|
||||
return $type;
|
||||
return InterfaceType::assertInterfaceType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,8 +148,11 @@ class FindBreakingChanges
|
||||
$dangerousChanges = [];
|
||||
foreach ($oldTypeMap as $oldTypeName => $oldTypeDefinition) {
|
||||
$newTypeDefinition = isset($newTypeMap[$oldTypeName]) ? $newTypeMap[$oldTypeName] : null;
|
||||
if (!($oldTypeDefinition instanceof ObjectType || $oldTypeDefinition instanceof InterfaceType) ||
|
||||
!($newTypeDefinition instanceof $oldTypeDefinition)) {
|
||||
if (
|
||||
!($oldTypeDefinition instanceof ObjectType || $oldTypeDefinition instanceof InterfaceType) ||
|
||||
!($newTypeDefinition instanceof ObjectType || $newTypeDefinition instanceof InterfaceType) ||
|
||||
!($newTypeDefinition instanceof $oldTypeDefinition)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -262,7 +265,11 @@ class FindBreakingChanges
|
||||
$breakingChanges = [];
|
||||
foreach ($oldTypeMap as $typeName => $oldType) {
|
||||
$newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null;
|
||||
if (!($oldType instanceof ObjectType || $oldType instanceof InterfaceType) || !($newType instanceof $oldType)) {
|
||||
if (
|
||||
!($oldType instanceof ObjectType || $oldType instanceof InterfaceType) ||
|
||||
!($newType instanceof ObjectType || $newType instanceof InterfaceType) ||
|
||||
!($newType instanceof $oldType)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$oldTypeFieldsDef = $oldType->getFields();
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Utils;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Type\Introspection;
|
||||
use GraphQL\Type\Schema;
|
||||
@ -142,9 +143,11 @@ class SchemaPrinter
|
||||
return self::printUnion($type, $options);
|
||||
} else if ($type instanceof EnumType) {
|
||||
return self::printEnum($type, $options);
|
||||
} else if ($type instanceof InputObjectType) {
|
||||
return self::printInputObject($type, $options);
|
||||
}
|
||||
Utils::invariant($type instanceof InputObjectType);
|
||||
return self::printInputObject($type, $options);
|
||||
|
||||
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
|
||||
}
|
||||
|
||||
private static function printScalar(ScalarType $type, array $options)
|
||||
|
@ -319,27 +319,28 @@ class DocumentValidator
|
||||
return [];
|
||||
}
|
||||
|
||||
Utils::invariant($type instanceof ScalarType, 'Must be a scalar type');
|
||||
/** @var ScalarType $type */
|
||||
|
||||
// Scalars determine if a literal values is valid via parseLiteral().
|
||||
try {
|
||||
$parseResult = $type->parseLiteral($valueNode);
|
||||
if (Utils::isInvalid($parseResult)) {
|
||||
if ($type instanceof ScalarType) {
|
||||
// Scalars determine if a literal values is valid via parseLiteral().
|
||||
try {
|
||||
$parseResult = $type->parseLiteral($valueNode);
|
||||
if (Utils::isInvalid($parseResult)) {
|
||||
$printed = Printer::doPrint($valueNode);
|
||||
return ["Expected type \"{$type->name}\", found $printed."];
|
||||
}
|
||||
} catch (\Exception $error) {
|
||||
$printed = Printer::doPrint($valueNode);
|
||||
return ["Expected type \"{$type->name}\", found $printed."];
|
||||
$message = $error->getMessage();
|
||||
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||
} catch (\Throwable $error) {
|
||||
$printed = Printer::doPrint($valueNode);
|
||||
$message = $error->getMessage();
|
||||
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||
}
|
||||
} catch (\Exception $error) {
|
||||
$printed = Printer::doPrint($valueNode);
|
||||
$message = $error->getMessage();
|
||||
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||
} catch (\Throwable $error) {
|
||||
$printed = Printer::doPrint($valueNode);
|
||||
$message = $error->getMessage();
|
||||
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -468,15 +468,6 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @it prohibits nesting NonNull inside NonNull
|
||||
*/
|
||||
public function testProhibitsNonNullNesting()
|
||||
{
|
||||
$this->setExpectedException('\Exception');
|
||||
new NonNull(new NonNull(Type::int()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it prohibits putting non-Object types in unions
|
||||
*/
|
||||
|
@ -1975,7 +1975,7 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
|
||||
// DESCRIBE: Type System: List must accept GraphQL types
|
||||
// DESCRIBE: Type System: List must accept only types
|
||||
|
||||
/**
|
||||
* @it accepts an type as item type of list
|
||||
@ -2022,7 +2022,7 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
|
||||
$this->fail("Expected exception not thrown for: " . Utils::printSafe($type));
|
||||
} catch (InvariantViolation $e) {
|
||||
$this->assertEquals(
|
||||
'Can only create List of a GraphQLType but got: ' . Utils::printSafe($type),
|
||||
'Expected '. Utils::printSafe($type) . ' to be a GraphQL type.',
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
@ -2030,7 +2030,7 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
|
||||
// DESCRIBE: Type System: NonNull must accept GraphQL types
|
||||
// DESCRIBE: Type System: NonNull must only accept non-nullable types
|
||||
|
||||
/**
|
||||
* @it accepts an type as nullable type of non-null
|
||||
@ -2057,8 +2057,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rejects a non-type as nullable type of non-null: ${type}
|
||||
|
||||
/**
|
||||
* @it rejects a non-type as nullable type of non-null
|
||||
*/
|
||||
@ -2079,7 +2077,7 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
|
||||
$this->fail("Expected exception not thrown for: " . Utils::printSafe($type));
|
||||
} catch (InvariantViolation $e) {
|
||||
$this->assertEquals(
|
||||
'Can only create NonNull of a Nullable GraphQLType but got: ' . Utils::printSafe($type),
|
||||
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL nullable type.',
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user