Support for union types when using buildSchema

* Adds support for resolving union/interface types when using a generated schema
* Move resolveType __typename checking into defaultResolveType
* Clean up existing tests and improve error messages

ref: graphql/graphql-js#947

# Conflicts:
#	src/Utils/BuildSchema.php
#	tests/Utils/BuildSchemaTest.php
This commit is contained in:
Daniel Tschinder 2018-02-09 12:54:34 +01:00
parent 7705e50e44
commit 4e26de3588
13 changed files with 198 additions and 261 deletions

View File

@ -1053,15 +1053,6 @@ class Executor
$runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
if (null === $runtimeType) {
if ($returnType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) {
Warning::warnOnce(
"GraphQL Interface Type `{$returnType->name}` returned `null` from it`s `resolveType` function ".
'for value: ' . Utils::printSafe($result) . '. Switching to slow resolution method using `isTypeOf` ' .
'of all possible implementations. It requires full schema scan and degrades query performance significantly. '.
' Make sure your `resolveType` always returns valid implementation or throws.',
Warning::WARNING_FULL_SCHEMA_SCAN
);
}
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
}
@ -1122,9 +1113,11 @@ class Executor
if (!$runtimeType instanceof ObjectType) {
throw new InvariantViolation(
"Abstract type {$returnType} must resolve to an Object type at runtime " .
"for field {$info->parentType}.{$info->fieldName} with " .
'value "' . Utils::printSafe($result) . '", received "'. Utils::printSafe($runtimeType) . '".'
"Abstract type {$returnType} must resolve to an Object type at " .
"runtime for field {$info->parentType}.{$info->fieldName} with " .
'value "' . Utils::printSafe($result) . '", received "'. Utils::printSafe($runtimeType) . '".' .
'Either the ' . $returnType . ' type should provide a "resolveType" ' .
'function or each possible types should provide an "isTypeOf" function.'
);
}
@ -1307,7 +1300,12 @@ class Executor
/**
* If a resolveType function is not given, then a default resolve behavior is
* used which tests each possible type for the abstract type by calling
* used which attempts two strategies:
*
* First, See if the provided value has a `__typename` field defined, if so, use
* that value as name of the resolved type.
*
* Otherwise, test each possible type for the abstract type by calling
* isTypeOf for the object being coerced, returning the first type that matches.
*
* @param $value
@ -1318,6 +1316,27 @@ class Executor
*/
private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractType $abstractType)
{
// First, look for `__typename`.
if (
$value !== null &&
is_array($value) &&
isset($value['__typename']) &&
is_string($value['__typename'])
) {
return $value['__typename'];
}
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) {
Warning::warnOnce(
"GraphQL Interface Type `{$abstractType->name}` returned `null` from it`s `resolveType` function ".
'for value: ' . Utils::printSafe($value) . '. Switching to slow resolution method using `isTypeOf` ' .
'of all possible implementations. It requires full schema scan and degrades query performance significantly. '.
' Make sure your `resolveType` always returns valid implementation or throws.',
Warning::WARNING_FULL_SCHEMA_SCAN
);
}
// Otherwise, test each possible type.
$possibleTypes = $info->schema->getPossibleTypes($abstractType);
$promisedIsTypeOfResults = [];

View File

@ -240,15 +240,6 @@ class ObjectType extends Type implements OutputType, CompositeType
"{$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.'
);
}
}
}
}

View File

@ -158,15 +158,6 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
"{$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.'
);
}
}
}
}

View File

@ -216,21 +216,21 @@ class BuildSchema
$directives = array_map([$this, 'getDirective'], $directiveDefs);
// If specified directives were not explicitly declared, add them.
$skip = array_reduce($directives, function($hasSkip, $directive) {
$skip = array_reduce($directives, function ($hasSkip, $directive) {
return $hasSkip || $directive->name == 'skip';
});
if (!$skip) {
$directives[] = Directive::skipDirective();
}
$include = array_reduce($directives, function($hasInclude, $directive) {
$include = array_reduce($directives, function ($hasInclude, $directive) {
return $hasInclude || $directive->name == 'include';
});
if (!$include) {
$directives[] = Directive::includeDirective();
}
$deprecated = array_reduce($directives, function($hasDeprecated, $directive) {
$deprecated = array_reduce($directives, function ($hasDeprecated, $directive) {
return $hasDeprecated || $directive->name == 'deprecated';
});
if (!$deprecated) {
@ -245,12 +245,12 @@ class BuildSchema
'subscription' => $subscriptionTypeName ?
$this->getObjectType($this->nodeMap[$subscriptionTypeName]) :
null,
'typeLoader' => function($name) {
'typeLoader' => function ($name) {
return $this->typeDefNamed($name);
},
'directives' => $directives,
'astNode' => $schemaDef,
'types' => function() {
'types' => function () {
$types = [];
foreach ($this->nodeMap as $name => $def) {
if (!isset($this->loadedTypeDefs[$name])) {
@ -269,7 +269,7 @@ class BuildSchema
return new Directive([
'name' => $directiveNode->name->value,
'description' => $this->getDescription($directiveNode),
'locations' => Utils::map($directiveNode->locations, function($node) {
'locations' => Utils::map($directiveNode->locations, function ($node) {
return $node->value;
}),
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
@ -342,7 +342,7 @@ class BuildSchema
$config = $fn($config, $this->nodeMap[$typeName], $this->nodeMap);
} catch (\Exception $e) {
throw new Error(
"Type config decorator passed to " . (static::class) . " threw an error ".
"Type config decorator passed to " . (static::class) . " threw an error " .
"when building $typeName type: {$e->getMessage()}",
null,
null,
@ -352,7 +352,7 @@ class BuildSchema
);
} catch (\Throwable $e) {
throw new Error(
"Type config decorator passed to " . (static::class) . " threw an error ".
"Type config decorator passed to " . (static::class) . " threw an error " .
"when building $typeName type: {$e->getMessage()}",
null,
null,
@ -363,7 +363,7 @@ class BuildSchema
}
if (!is_array($config) || isset($config[0])) {
throw new Error(
"Type config decorator passed to " . (static::class) . " is expected to return an array, but got ".
"Type config decorator passed to " . (static::class) . " is expected to return an array, but got " .
Utils::getVariableType($config)
);
}
@ -433,10 +433,10 @@ class BuildSchema
return [
'name' => $typeName,
'description' => $this->getDescription($def),
'fields' => function() use ($def) {
'fields' => function () use ($def) {
return $this->makeFieldDefMap($def);
},
'interfaces' => function() use ($def) {
'interfaces' => function () use ($def) {
return $this->makeImplementedInterfaces($def);
},
'astNode' => $def
@ -450,7 +450,7 @@ class BuildSchema
function ($field) {
return $field->name->value;
},
function($field) {
function ($field) {
return [
'type' => $this->produceOutputType($field->type),
'description' => $this->getDescription($field),
@ -479,7 +479,7 @@ class BuildSchema
function ($value) {
return $value->name->value;
},
function($value) {
function ($value) {
$type = $this->produceInputType($value->type);
$config = [
'name' => $value->name->value,
@ -501,13 +501,10 @@ class BuildSchema
return [
'name' => $typeName,
'description' => $this->getDescription($def),
'fields' => function() use ($def) {
'fields' => function () use ($def) {
return $this->makeFieldDefMap($def);
},
'astNode' => $def,
'resolveType' => function() {
$this->cannotExecuteSchema();
}
'astNode' => $def
];
}
@ -519,10 +516,10 @@ class BuildSchema
'astNode' => $def,
'values' => Utils::keyValMap(
$def->values,
function($enumValue) {
function ($enumValue) {
return $enumValue->name->value;
},
function($enumValue) {
function ($enumValue) {
return [
'description' => $this->getDescription($enumValue),
'deprecationReason' => $this->getDeprecationReason($enumValue),
@ -538,11 +535,10 @@ class BuildSchema
return [
'name' => $def->name->value,
'description' => $this->getDescription($def),
'types' => Utils::map($def->types, function($typeNode) {
'types' => Utils::map($def->types, function ($typeNode) {
return $this->produceObjectType($typeNode);
}),
'astNode' => $def,
'resolveType' => [$this, 'cannotExecuteSchema']
'astNode' => $def
];
}
@ -552,17 +548,17 @@ class BuildSchema
'name' => $def->name->value,
'description' => $this->getDescription($def),
'astNode' => $def,
'serialize' => function() {
'serialize' => function () {
return false;
},
// Note: validation calls the parse functions to determine if a
// literal value is correct. Returning null would cause use of custom
// scalars to always fail validation. Returning false causes them to
// always pass validation.
'parseValue' => function() {
'parseValue' => function () {
return false;
},
'parseLiteral' => function() {
'parseLiteral' => function () {
return false;
}
];
@ -573,7 +569,9 @@ class BuildSchema
return [
'name' => $def->name->value,
'description' => $this->getDescription($def),
'fields' => function() use ($def) { return $this->makeInputValues($def->fields); },
'fields' => function () use ($def) {
return $this->makeInputValues($def->fields);
},
'astNode' => $def,
];
}
@ -611,7 +609,7 @@ class BuildSchema
{
$loc = $node->loc;
if (!$loc || !$loc->startToken) {
return ;
return;
}
$comments = [];
$token = $loc->startToken->prev;
@ -644,12 +642,4 @@ class BuildSchema
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
return self::buildAST($doc, $typeConfigDecorator);
}
public function cannotExecuteSchema()
{
throw new Error(
'Generated Schema cannot use Interface or Union types for execution.'
);
}
}
}

View File

@ -87,9 +87,7 @@ class AbstractPromiseTest extends \PHPUnit_Framework_TestCase
}
}';
Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN);
$result = GraphQL::execute($schema, $query);
Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN);
$expected = [
'data' => [
@ -174,9 +172,7 @@ class AbstractPromiseTest extends \PHPUnit_Framework_TestCase
}
}';
Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN);
$result = GraphQL::execute($schema, $query);
Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN);
$expected = [
'data' => [

View File

@ -256,9 +256,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
]
];
Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN);
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN);
}
/**
@ -294,9 +292,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
]
];
Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN);
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN);
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true));
}
/**
@ -351,9 +347,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase
]
];
Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN);
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN);
}
/**

View File

@ -74,10 +74,7 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$this->objectType = new ObjectType([
'name' => 'Object',
'isTypeOf' => function() {return true;}
]);
$this->objectType = new ObjectType(['name' => 'Object']);
$this->interfaceType = new InterfaceType(['name' => 'Interface']);
$this->unionType = new UnionType(['name' => 'Union', 'types' => [$this->objectType]]);
$this->enumType = new EnumType(['name' => 'Enum']);
@ -363,7 +360,6 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
'f' => ['type' => Type::int()]
],
'interfaces' => [$someInterface],
'isTypeOf' => function() {return true;}
]);
$schema = new Schema([
@ -391,7 +387,6 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
'f' => ['type' => Type::int()]
],
'interfaces' => function() use (&$someInterface) { return [$someInterface]; },
'isTypeOf' => function() {return true;}
]);
$someInterface = new InterfaceType([

View File

@ -58,24 +58,15 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
$this->ObjectWithIsTypeOf = new ObjectType([
'name' => 'ObjectWithIsTypeOf',
'isTypeOf' => function() {
return true;
},
'fields' => [ 'f' => [ 'type' => Type::string() ]]
]);
$this->SomeUnionType = new UnionType([
'name' => 'SomeUnion',
'resolveType' => function() {
return null;
},
'types' => [ $this->SomeObjectType ]
]);
$this->SomeInterfaceType = new InterfaceType([
'name' => 'SomeInterface',
'resolveType' => function() {
return null;
},
'fields' => [ 'f' => ['type' => Type::string() ]]
]);
@ -404,7 +395,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function() {},
'fields' => [ 'f' => [ 'type' => Type::string() ]],
]);
@ -736,8 +726,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterfaceType = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => ['f' => ['type' => Type::string()]]
]);
@ -756,8 +744,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterfaceType = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => ['f' => ['type' => Type::string()]]
]);
@ -795,14 +781,11 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$NonUniqInterface = new InterfaceType([
'name' => 'NonUniqInterface',
'resolveType' => function () {
},
'fields' => ['f' => ['type' => Type::string()]],
]);
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function(){},
'fields' => ['f' => ['type' => Type::string()]],
]);
@ -851,9 +834,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
return null;
},
'types' => [$this->SomeObjectType],
]));
$schema->assertValid();
@ -866,9 +846,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
return null;
},
'types' => function () {
return [$this->SomeObjectType];
},
@ -887,7 +864,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
);
$this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function() {return null;}
]));
}
@ -898,8 +874,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
},
'types' => []
]));
@ -921,8 +895,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
);
$this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
},
'types' => $this->SomeObjectType
]));
}
@ -934,7 +906,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function(){},
'types' => [
$this->SomeObjectType,
$this->SomeObjectType,
@ -1193,8 +1164,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterfaceType = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => ['f' => ['type' => Type::string()]]
]);
@ -1234,8 +1203,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterfaceType = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => ['f' => ['type' => Type::string()]]
]);
@ -1270,32 +1237,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
$type->assertValid();
}
/**
* @it rejects an Interface type not defining resolveType with implementing type not defining isTypeOf
*/
public function testRejectsAnInterfaceTypeNotDefiningResolveTypeWithImplementingTypeNotDefiningIsTypeOf()
{
$InterfaceTypeWithoutResolveType = new InterfaceType([
'name' => 'InterfaceTypeWithoutResolveType',
'fields' => ['f' => ['type' => Type::string()]]
]);
$schema = $this->schemaWithFieldType(new ObjectType([
'name' => 'SomeObject',
'interfaces' => [$InterfaceTypeWithoutResolveType],
'fields' => ['f' => ['type' => Type::string()]]
]));
$this->setExpectedException(
InvariantViolation::class,
'Interface Type InterfaceTypeWithoutResolveType does not provide a "resolveType" function and implementing '.
'Type SomeObject does not provide a "isTypeOf" function. There is no way to resolve this implementing type '.
'during execution.'
);
$schema->assertValid();
}
// DESCRIBE: Type System: Union types must be resolvable
/**
@ -1305,8 +1246,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
},
'types' => [$this->SomeObjectType],
]));
$schema->assertValid();
@ -1332,8 +1271,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'resolveType' => function () {
},
'types' => [$this->ObjectWithIsTypeOf],
]));
$schema->assertValid();
@ -1358,25 +1295,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
$schema->assertValid();
}
/**
* @it rejects a Union type not defining resolveType of Object types not defining isTypeOf
*/
public function testRejectsAUnionTypeNotDefiningResolveTypeOfObjectTypesNotDefiningIsTypeOf()
{
$schema = $this->schemaWithFieldType(new UnionType([
'name' => 'SomeUnion',
'types' => [$this->SomeObjectType],
]));
$this->setExpectedException(
InvariantViolation::class,
'Union type "SomeUnion" does not provide a "resolveType" function and possible type "SomeObject" '.
'does not provide an "isTypeOf" function. There is no way to resolve this possible type during execution.'
);
$schema->assertValid();
}
// DESCRIBE: Type System: Scalar types must be serializable
/**
@ -1747,8 +1665,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterfaceType = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => ['f' => ['type' => Type::string()]]
]);
@ -2085,8 +2001,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2121,8 +2035,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2158,8 +2070,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2195,8 +2105,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2238,8 +2146,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2274,8 +2180,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => Type::string()]
]
@ -2318,8 +2222,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => $TypeA]
]
@ -2350,8 +2252,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => function () use (&$AnotherInterface) {
return [
'field' => ['type' => $AnotherInterface]
@ -2380,8 +2280,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => $this->SomeUnionType]
]
@ -2406,8 +2304,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2445,8 +2341,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => [
'type' => Type::string(),
@ -2487,8 +2381,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => Type::nonNull(Type::listOf(Type::string()))]
]
@ -2513,8 +2405,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => Type::listOf(Type::string())]
]
@ -2545,8 +2435,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => Type::string()]
]
@ -2575,8 +2463,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => Type::string()]
]
@ -2601,8 +2487,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$AnotherInterface = new InterfaceType([
'name' => 'AnotherInterface',
'resolveType' => function () {
},
'fields' => [
'field' => ['type' => Type::nonNull(Type::string())]
]
@ -2820,8 +2704,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
{
$BadUnionType = new UnionType([
'name' => 'BadUnion',
'resolveType' => function () {
},
'types' => [$type],
]);
return new Schema([

View File

@ -36,8 +36,8 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase
}
'));
$result = GraphQL::execute($schema, '{ str }', ['str' => 123]);
$this->assertEquals($result['data'], ['str' => 123]);
$result = GraphQL::executeQuery($schema, '{ str }', ['str' => 123]);
$this->assertEquals(['str' => 123], $result->toArray(true)['data']);
}
/**
@ -52,7 +52,7 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase
}
");
$result = GraphQL::execute(
$result = GraphQL::executeQuery(
$schema,
'{ add(x: 34, y: 55) }',
[
@ -61,7 +61,7 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase
}
]
);
$this->assertEquals($result, ['data' => ['add' => 89]]);
$this->assertEquals(['data' => ['add' => 89]], $result->toArray(true));
}
/**
@ -447,6 +447,135 @@ type WorldTwo {
$this->assertEquals($output, $body);
}
/**
* @it Specifying Union type using __typename
*/
public function testSpecifyingUnionTypeUsingTypename()
{
$schema = BuildSchema::buildAST(Parser::parse('
schema {
query: Root
}
type Root {
fruits: [Fruit]
}
union Fruit = Apple | Banana
type Apple {
color: String
}
type Banana {
length: Int
}
'));
$query = '
{
fruits {
... on Apple {
color
}
... on Banana {
length
}
}
}
';
$root = [
'fruits' => [
[
'color' => 'green',
'__typename' => 'Apple',
],
[
'length' => 5,
'__typename' => 'Banana',
]
]
];
$expected = [
'data' => [
'fruits' => [
['color' => 'green'],
['length' => 5],
]
]
];
$result = GraphQL::executeQuery($schema, $query, $root);
$this->assertEquals($expected, $result->toArray(true));
}
/**
* @it Specifying Interface type using __typename
*/
public function testSpecifyingInterfaceUsingTypename()
{
$schema = BuildSchema::buildAST(Parser::parse('
schema {
query: Root
}
type Root {
characters: [Character]
}
interface Character {
name: String!
}
type Human implements Character {
name: String!
totalCredits: Int
}
type Droid implements Character {
name: String!
primaryFunction: String
}
'));
$query = '
{
characters {
name
... on Human {
totalCredits
}
... on Droid {
primaryFunction
}
}
}
';
$root = [
'characters' => [
[
'name' => 'Han Solo',
'totalCredits' => 10,
'__typename' => 'Human',
],
[
'name' => 'R2-D2',
'primaryFunction' => 'Astromech',
'__typename' => 'Droid',
]
]
];
$expected = [
'data' => [
'characters' => [
['name' => 'Han Solo', 'totalCredits' => 10],
['name' => 'R2-D2', 'primaryFunction' => 'Astromech'],
]
]
];
$result = GraphQL::executeQuery($schema, $query, $root);
$this->assertEquals($expected, $result->toArray(true));
}
/**
* @it CustomScalar
*/
@ -1093,9 +1222,8 @@ interface Hello {
$this->assertInstanceOf(InterfaceTypeDefinitionNode::class, $node);
$this->assertEquals('Hello', $defaultConfig['name']);
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
$this->assertInstanceOf(\Closure::class, $defaultConfig['resolveType']);
$this->assertArrayHasKey('description', $defaultConfig);
$this->assertCount(5, $defaultConfig);
$this->assertCount(4, $defaultConfig);
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Hello', $schema->getType('Hello')->description);
}

View File

@ -1,8 +1,4 @@
<?php
/**
* FindBreakingChanges tests
*/
namespace GraphQL\Tests\Utils;
use GraphQL\Type\Definition\EnumType;
@ -16,7 +12,6 @@ use GraphQL\Utils\FindBreakingChanges;
class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
{
private $queryType;
public function setUp()
@ -88,7 +83,7 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
$unionType = new UnionType([
'name' => 'Type1',
'types' => [new ObjectType(['name' => 'blah'])],
'types' => [$objectType],
]);
$oldSchema = new Schema([
@ -510,16 +505,12 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
$oldUnionType = new UnionType([
'name' => 'UnionType1',
'types' => [$type1, $type2],
'resolveType' => function () {
}
]);
$newUnionType = new UnionType([
'name' => 'UnionType1',
'types' => [$type1a, $type3],
'resolveType' => function () {
}
]);
$oldSchema = new Schema([
@ -978,8 +969,6 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
'fields' => [
'field1' => Type::string()
],
'resolveType' => function () {
}
]);
$oldType = new ObjectType([
'name' => 'Type1',
@ -1099,15 +1088,11 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
$unionTypeThatLosesATypeOld = new UnionType([
'name' => 'UnionTypeThatLosesAType',
'types' => [$typeInUnion1, $typeInUnion2],
'resolveType' => function () {
}
]);
$unionTypeThatLosesATypeNew = new UnionType([
'name' => 'UnionTypeThatLosesAType',
'types' => [$typeInUnion1],
'resolveType' => function () {
}
]);
$enumTypeThatLosesAValueOld = new EnumType([
@ -1132,8 +1117,6 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
'fields' => [
'field1' => Type::string()
],
'resolveType' => function () {
}
]);
$typeThatLosesInterfaceOld = new ObjectType([
@ -1353,15 +1336,11 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
$oldUnionType = new UnionType([
'name' => 'UnionType1',
'types' => [$type1],
'resolveType' => function () {
}
]);
$newUnionType = new UnionType([
'name' => 'UnionType1',
'types' => [$type1a, $type2],
'resolveType' => function () {
}
]);
$oldSchema = new Schema([
@ -1452,15 +1431,11 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
$unionTypeThatGainsATypeOld = new UnionType([
'name' => 'UnionType1',
'types' => [$typeInUnion1],
'resolveType' => function () {
}
]);
$unionTypeThatGainsATypeNew = new UnionType([
'name' => 'UnionType1',
'types' => [$typeInUnion1, $typeInUnion2],
'resolveType' => function () {
}
]);
$oldSchema = new Schema([
@ -1498,4 +1473,4 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedDangerousChanges, FindBreakingChanges::findDangerousChanges($oldSchema, $newSchema));
}
}
}

View File

@ -360,7 +360,6 @@ type Root {
{
$fooType = new InterfaceType([
'name' => 'Foo',
'resolveType' => function() { return null; },
'fields' => ['str' => ['type' => Type::string()]]
]);
@ -406,13 +405,11 @@ type Root {
{
$fooType = new InterfaceType([
'name' => 'Foo',
'resolveType' => function() { return null; },
'fields' => ['str' => ['type' => Type::string()]]
]);
$baazType = new InterfaceType([
'name' => 'Baaz',
'resolveType' => function() { return null; },
'fields' => ['int' => ['type' => Type::int()]]
]);
@ -476,13 +473,11 @@ type Root {
$singleUnion = new UnionType([
'name' => 'SingleUnion',
'resolveType' => function() { return null; },
'types' => [$fooType]
]);
$multipleUnion = new UnionType([
'name' => 'MultipleUnion',
'resolveType' => function() { return null; },
'types' => [$fooType, $barType]
]);
@ -1098,4 +1093,4 @@ enum __TypeKind {
EOT;
$this->assertEquals($introspectionSchema, $output);
}
}
}

View File

@ -795,7 +795,6 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
$SomeBox = new InterfaceType([
'name' => 'SomeBox',
'resolveType' => function() use (&$StringBox) {return $StringBox;},
'fields' => function() use (&$SomeBox) {
return [
'deepBox' => ['type' => $SomeBox],
@ -837,7 +836,6 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
$NonNullStringBox1 = new InterfaceType([
'name' => 'NonNullStringBox1',
'resolveType' => function() use (&$StringBox) {return $StringBox;},
'fields' => [
'scalar' => [ 'type' => Type::nonNull(Type::string()) ]
]
@ -855,7 +853,6 @@ class OverlappingFieldsCanBeMergedTest extends TestCase
$NonNullStringBox2 = new InterfaceType([
'name' => 'NonNullStringBox2',
'resolveType' => function() use (&$StringBox) {return $StringBox;},
'fields' => [
'scalar' => ['type' => Type::nonNull(Type::string())]
]

View File

@ -67,7 +67,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$Dog = new ObjectType([
'name' => 'Dog',
'isTypeOf' => function() {return true;},
'fields' => [
'name' => [
'type' => Type::string(),
@ -94,7 +93,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$Cat = new ObjectType([
'name' => 'Cat',
'isTypeOf' => function() {return true;},
'fields' => function() use (&$FurColor) {
return [
'name' => [
@ -113,10 +111,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$CatOrDog = new UnionType([
'name' => 'CatOrDog',
'types' => [$Dog, $Cat],
'resolveType' => function($value) {
// not used for validation
return null;
}
]);
$Intelligent = new InterfaceType([
@ -129,7 +123,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$Human = null;
$Human = new ObjectType([
'name' => 'Human',
'isTypeOf' => function() {return true;},
'interfaces' => [$Being, $Intelligent],
'fields' => function() use (&$Human, $Pet) {
return [
@ -146,7 +139,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$Alien = new ObjectType([
'name' => 'Alien',
'isTypeOf' => function() {return true;},
'interfaces' => [$Being, $Intelligent],
'fields' => [
'iq' => ['type' => Type::int()],
@ -161,19 +153,11 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$DogOrHuman = new UnionType([
'name' => 'DogOrHuman',
'types' => [$Dog, $Human],
'resolveType' => function() {
// not used for validation
return null;
}
]);
$HumanOrAlien = new UnionType([
'name' => 'HumanOrAlien',
'types' => [$Human, $Alien],
'resolveType' => function() {
// not used for validation
return null;
}
]);
$FurColor = new EnumType([