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:
Daniel Tschinder 2018-02-13 18:04:03 +01:00
parent 60df83f47e
commit 9387548aa1
13 changed files with 181 additions and 134 deletions

View File

@ -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)
```

View File

@ -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[]
*/

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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[]
*/

View File

@ -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

View File

@ -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,11 +206,7 @@ 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);
@ -252,6 +249,9 @@ class AST
throw new InvariantViolation('Cannot convert value to AST: ' . Utils::printSafe($serialized));
}
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
/**
* Produces a PHP value given a GraphQL Value AST.
*
@ -395,9 +395,7 @@ 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.
@ -416,6 +414,9 @@ class AST
return $result;
}
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
/**
* Produces a PHP value given a GraphQL Value AST.
*
@ -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,11 +498,13 @@ class AST
$innerType = self::typeFromAST($schema, $inputTypeNode->type);
return $innerType ? new NonNull($innerType) : null;
}
Utils::invariant($inputTypeNode && $inputTypeNode instanceof NamedTypeNode, 'Must be a named type');
if ($inputTypeNode instanceof NamedTypeNode) {
return $schema->getType($inputTypeNode->name->value);
}
throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.');
}
/**
* Returns true if the provided valueNode is a variable which is not defined
* in the set of variables.

View File

@ -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);
}
/**

View File

@ -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();

View File

@ -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,11 +143,13 @@ class SchemaPrinter
return self::printUnion($type, $options);
} else if ($type instanceof EnumType) {
return self::printEnum($type, $options);
}
Utils::invariant($type instanceof InputObjectType);
} else if ($type instanceof InputObjectType) {
return self::printInputObject($type, $options);
}
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
private static function printScalar(ScalarType $type, array $options)
{
return self::printDescription($options, $type) . "scalar {$type->name}";

View File

@ -319,9 +319,7 @@ class DocumentValidator
return [];
}
Utils::invariant($type instanceof ScalarType, 'Must be a scalar type');
/** @var ScalarType $type */
if ($type instanceof ScalarType) {
// Scalars determine if a literal values is valid via parseLiteral().
try {
$parseResult = $type->parseLiteral($valueNode);
@ -342,6 +340,9 @@ class DocumentValidator
return [];
}
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
/**
* This uses a specialized visitor which runs multiple visitors in parallel,
* while maintaining the visitor skip and break API.

View File

@ -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
*/

View File

@ -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()
);
}