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 Schema $schema
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
* @return Type * @return Type
* @throws InvariantViolation * @throws \Exception
*/ */
static function typeFromAST(GraphQL\Type\Schema $schema, $inputTypeNode) 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 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[] * @var FieldDefinition[]
*/ */

View File

@ -20,12 +20,7 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
*/ */
public function __construct($type) public function __construct($type)
{ {
if (!$type instanceof Type && !is_callable($type)) { $this->ofType = Type::assertType($type);
throw new InvariantViolation(
'Can only create List of a GraphQLType but got: ' . Utils::printSafe($type)
);
}
$this->ofType = $type;
} }
/** /**

View File

@ -11,7 +11,35 @@ use GraphQL\Utils\Utils;
class NonNull extends Type implements WrappingType, OutputType, InputType 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; private $ofType;
@ -21,37 +49,17 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
*/ */
public function __construct($type) public function __construct($type)
{ {
if (!$type instanceof Type && !is_callable($type)) { $this->ofType = self::assertNullableType($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;
} }
/** /**
* @param bool $recurse * @param bool $recurse
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
* @throws InvariantViolation * @throws InvariantViolation
*/ */
public function getWrappedType($recurse = false) public function getWrappedType($recurse = false)
{ {
$type = $this->ofType; $type = $this->ofType;
Utils::invariant(
!($type instanceof NonNull),
'Cannot nest NonNull inside NonNull'
);
return ($recurse && $type instanceof WrappingType) ? $type->getWrappedType($recurse) : $type; 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 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[] * @var FieldDefinition[]
*/ */

View File

@ -4,8 +4,10 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ListType; use GraphQL\Language\AST\ListType;
use GraphQL\Language\AST\NamedType; use GraphQL\Language\AST\NamedType;
use GraphQL\Language\AST\NonNullType;
use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Utils\Utils;
/** /**
* Registry of standard GraphQL types * Registry of standard GraphQL types
@ -218,11 +220,25 @@ abstract class Type implements \JsonSerializable
$type instanceof UnionType || $type instanceof UnionType ||
$type instanceof EnumType || $type instanceof EnumType ||
$type instanceof InputObjectType || $type instanceof InputObjectType ||
$type instanceof ListType || $type instanceof ListOfType ||
$type instanceof NonNull $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 * @api
* @param Type $type * @param Type $type

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
@ -205,11 +206,7 @@ class AST
return new ObjectValueNode(['fields' => $fieldNodes]); return new ObjectValueNode(['fields' => $fieldNodes]);
} }
Utils::invariant( if ($type instanceof ScalarType || $type instanceof EnumType) {
$type instanceof ScalarType || $type instanceof EnumType,
"Must provide Input Type, cannot use: " . Utils::printSafe($type)
);
// Since value is an internally represented value, it must be serialized // Since value is an internally represented value, it must be serialized
// to an externally represented value before converting into an AST. // to an externally represented value before converting into an AST.
$serialized = $type->serialize($value); $serialized = $type->serialize($value);
@ -252,6 +249,9 @@ class AST
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) . '.');
}
/** /**
* Produces a PHP value given a GraphQL Value AST. * Produces a PHP value given a GraphQL Value AST.
* *
@ -395,9 +395,7 @@ class AST
return $enumValue->value; return $enumValue->value;
} }
Utils::invariant($type instanceof ScalarType, 'Must be scalar type'); if ($type instanceof ScalarType) {
/** @var ScalarType $type */
// Scalars fulfill parsing a literal value via parseLiteral(). // Scalars fulfill parsing a literal value via parseLiteral().
// Invalid values represent a failure to parse correctly, in which case // Invalid values represent a failure to parse correctly, in which case
// no value is returned. // no value is returned.
@ -416,6 +414,9 @@ class AST
return $result; return $result;
} }
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
/** /**
* Produces a PHP value given a GraphQL Value AST. * Produces a PHP value given a GraphQL Value AST.
* *
@ -473,9 +474,9 @@ class AST
return ($variables && isset($variables[$variableName]) && !Utils::isInvalid($variables[$variableName])) return ($variables && isset($variables[$variableName]) && !Utils::isInvalid($variables[$variableName]))
? $variables[$variableName] ? $variables[$variableName]
: null; : 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 Schema $schema
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
* @return Type * @return Type
* @throws InvariantViolation * @throws \Exception
*/ */
public static function typeFromAST(Schema $schema, $inputTypeNode) public static function typeFromAST(Schema $schema, $inputTypeNode)
{ {
@ -497,11 +498,13 @@ class AST
$innerType = self::typeFromAST($schema, $inputTypeNode->type); $innerType = self::typeFromAST($schema, $inputTypeNode->type);
return $innerType ? new NonNull($innerType) : null; return $innerType ? new NonNull($innerType) : null;
} }
if ($inputTypeNode instanceof NamedTypeNode) {
Utils::invariant($inputTypeNode && $inputTypeNode instanceof NamedTypeNode, 'Must be a named type');
return $schema->getType($inputTypeNode->name->value); 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 * Returns true if the provided valueNode is a variable which is not defined
* in the set of variables. * in the set of variables.

View File

@ -75,8 +75,7 @@ class ASTDefinitionBuilder
} }
if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) { if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) {
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type); $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
Utils::invariant(!($wrappedType instanceof NonNull), 'No nesting nonnull.'); return Type::nonNull(NonNull::assertNullableType($wrappedType));
return Type::nonNull($wrappedType);
} }
return $innerType; return $innerType;
} }
@ -159,8 +158,7 @@ class ASTDefinitionBuilder
public function buildObjectType($typeNode) public function buildObjectType($typeNode)
{ {
$type = $this->buildType($typeNode); $type = $this->buildType($typeNode);
Utils::invariant($type instanceof ObjectType, 'Expected Object type.' . get_class($type)); return ObjectType::assertObjectType($type);
return $type;
} }
/** /**
@ -171,8 +169,7 @@ class ASTDefinitionBuilder
public function buildInterfaceType($typeNode) public function buildInterfaceType($typeNode)
{ {
$type = $this->buildType($typeNode); $type = $this->buildType($typeNode);
Utils::invariant($type instanceof InterfaceType, 'Expected Interface type.'); return InterfaceType::assertInterfaceType($type);
return $type;
} }
/** /**

View File

@ -148,8 +148,11 @@ class FindBreakingChanges
$dangerousChanges = []; $dangerousChanges = [];
foreach ($oldTypeMap as $oldTypeName => $oldTypeDefinition) { foreach ($oldTypeMap as $oldTypeName => $oldTypeDefinition) {
$newTypeDefinition = isset($newTypeMap[$oldTypeName]) ? $newTypeMap[$oldTypeName] : null; $newTypeDefinition = isset($newTypeMap[$oldTypeName]) ? $newTypeMap[$oldTypeName] : null;
if (!($oldTypeDefinition instanceof ObjectType || $oldTypeDefinition instanceof InterfaceType) || if (
!($newTypeDefinition instanceof $oldTypeDefinition)) { !($oldTypeDefinition instanceof ObjectType || $oldTypeDefinition instanceof InterfaceType) ||
!($newTypeDefinition instanceof ObjectType || $newTypeDefinition instanceof InterfaceType) ||
!($newTypeDefinition instanceof $oldTypeDefinition)
) {
continue; continue;
} }
@ -262,7 +265,11 @@ class FindBreakingChanges
$breakingChanges = []; $breakingChanges = [];
foreach ($oldTypeMap as $typeName => $oldType) { foreach ($oldTypeMap as $typeName => $oldType) {
$newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; $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; continue;
} }
$oldTypeFieldsDef = $oldType->getFields(); $oldTypeFieldsDef = $oldType->getFields();

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
@ -142,11 +143,13 @@ class SchemaPrinter
return self::printUnion($type, $options); return self::printUnion($type, $options);
} else if ($type instanceof EnumType) { } else if ($type instanceof EnumType) {
return self::printEnum($type, $options); return self::printEnum($type, $options);
} } else if ($type instanceof InputObjectType) {
Utils::invariant($type instanceof InputObjectType);
return self::printInputObject($type, $options); return self::printInputObject($type, $options);
} }
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
private static function printScalar(ScalarType $type, array $options) private static function printScalar(ScalarType $type, array $options)
{ {
return self::printDescription($options, $type) . "scalar {$type->name}"; return self::printDescription($options, $type) . "scalar {$type->name}";

View File

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