mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 06:16:05 +03:00
Ensure interface has at least 1 concrete type
This commit is contained in:
parent
779774b162
commit
7c19777dff
@ -263,6 +263,9 @@ class SchemaValidationContext
|
||||
} elseif ($type instanceof InterfaceType) {
|
||||
// Ensure fields are valid.
|
||||
$this->validateFields($type);
|
||||
|
||||
// Ensure Interfaces include at least 1 Object type.
|
||||
$this->validateInterfaces($type);
|
||||
} elseif ($type instanceof UnionType) {
|
||||
// Ensure Unions include valid member types.
|
||||
$this->validateUnionMembers($type);
|
||||
@ -504,6 +507,23 @@ class SchemaValidationContext
|
||||
}
|
||||
}
|
||||
|
||||
private function validateInterfaces(InterfaceType $iface)
|
||||
{
|
||||
$possibleTypes = $this->schema->getPossibleTypes($iface);
|
||||
|
||||
if (count($possibleTypes) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->reportError(
|
||||
sprintf(
|
||||
'Interface %s must be implemented by at least one Object type.',
|
||||
$iface->name
|
||||
),
|
||||
$iface->astNode
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InterfaceType $iface
|
||||
*
|
||||
|
@ -21,8 +21,8 @@ use GraphQL\Utils\Utils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function print_r;
|
||||
use function sprintf;
|
||||
|
||||
class ValidationTest extends TestCase
|
||||
@ -74,6 +74,11 @@ class ValidationTest extends TestCase
|
||||
},
|
||||
]);
|
||||
|
||||
$this->SomeInterfaceType = new InterfaceType([
|
||||
'name' => 'SomeInterface',
|
||||
'fields' => ['f' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$this->SomeObjectType = new ObjectType([
|
||||
'name' => 'SomeObject',
|
||||
'fields' => ['f' => ['type' => Type::string()]],
|
||||
@ -87,11 +92,6 @@ class ValidationTest extends TestCase
|
||||
'types' => [$this->SomeObjectType],
|
||||
]);
|
||||
|
||||
$this->SomeInterfaceType = new InterfaceType([
|
||||
'name' => 'SomeInterface',
|
||||
'fields' => ['f' => ['type' => Type::string()]],
|
||||
]);
|
||||
|
||||
$this->SomeEnumType = new EnumType([
|
||||
'name' => 'SomeEnum',
|
||||
'values' => [
|
||||
@ -339,34 +339,40 @@ class ValidationTest extends TestCase
|
||||
*/
|
||||
private function assertContainsValidationMessage($array, $messages)
|
||||
{
|
||||
self::assertCount(
|
||||
count($messages),
|
||||
$array,
|
||||
sprintf('For messages: %s', $messages[0]['message']) . "\n" .
|
||||
"Received: \n" .
|
||||
implode(
|
||||
"\n",
|
||||
array_map(
|
||||
static function ($error) {
|
||||
return $error->getMessage();
|
||||
},
|
||||
$array
|
||||
)
|
||||
$allErrors = implode(
|
||||
"\n",
|
||||
array_map(
|
||||
static function ($error) {
|
||||
return $error->getMessage();
|
||||
},
|
||||
$array
|
||||
)
|
||||
);
|
||||
foreach ($array as $index => $error) {
|
||||
if (! isset($messages[$index]) || ! $error instanceof Error) {
|
||||
self::fail('Received unexpected error: ' . $error->getMessage());
|
||||
|
||||
foreach ($messages as $expected) {
|
||||
$msg = $expected['message'];
|
||||
$locations = $expected['locations'] ?? [];
|
||||
foreach ($array as $actual) {
|
||||
if ($actual instanceof Error && $actual->getMessage() === $msg) {
|
||||
$actualLocations = [];
|
||||
foreach ($actual->getLocations() as $location) {
|
||||
$actualLocations[] = $location->toArray();
|
||||
}
|
||||
self::assertEquals(
|
||||
$locations,
|
||||
$actualLocations,
|
||||
sprintf(
|
||||
'Locations do not match for error: %s\n\nExpected:\n%s\n\nActual:\n%s',
|
||||
$msg,
|
||||
print_r($locations, true),
|
||||
print_r($actualLocations, true)
|
||||
)
|
||||
);
|
||||
// Found and valid, so check the next message (and don't fail)
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
self::assertEquals($messages[$index]['message'], $error->getMessage());
|
||||
$errorLocations = [];
|
||||
foreach ($error->getLocations() as $location) {
|
||||
$errorLocations[] = $location->toArray();
|
||||
}
|
||||
self::assertEquals(
|
||||
$messages[$index]['locations'] ?? [],
|
||||
$errorLocations
|
||||
);
|
||||
self::fail(sprintf("Expected error not found:\n%s\n\nActual errors:\n%s", $msg, $allErrors));
|
||||
}
|
||||
}
|
||||
|
||||
@ -598,12 +604,18 @@ class ValidationTest extends TestCase
|
||||
*/
|
||||
private function schemaWithFieldType($type) : Schema
|
||||
{
|
||||
$ifaceImplementation = new ObjectType([
|
||||
'name' => 'SomeInterfaceImplementation',
|
||||
'fields' => ['f' => ['type' => Type::string()]],
|
||||
'interfaces' => [ $this->SomeInterfaceType ],
|
||||
]);
|
||||
|
||||
return new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => ['f' => ['type' => $type]],
|
||||
]),
|
||||
'types' => [$type],
|
||||
'types' => [$type, $ifaceImplementation],
|
||||
]);
|
||||
}
|
||||
|
||||
@ -1237,6 +1249,14 @@ class ValidationTest extends TestCase
|
||||
],
|
||||
]);
|
||||
|
||||
$BadImplementingType = new ObjectType([
|
||||
'name' => 'BadImplementing',
|
||||
'interfaces' => [ $BadInterfaceType ],
|
||||
'fields' => [
|
||||
'badField' => [ 'type' => $fieldType ],
|
||||
],
|
||||
]);
|
||||
|
||||
return new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
@ -1244,6 +1264,7 @@ class ValidationTest extends TestCase
|
||||
'f' => ['type' => $BadInterfaceType],
|
||||
],
|
||||
]),
|
||||
'types' => [ $BadImplementingType, $this->SomeObjectType ],
|
||||
]);
|
||||
}
|
||||
|
||||
@ -1308,6 +1329,30 @@ class ValidationTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('rejects an interface not implemented by at least one object')
|
||||
*/
|
||||
public function testRejectsAnInterfaceNotImplementedByAtLeastOneObject()
|
||||
{
|
||||
$schema = BuildSchema::build('
|
||||
type Query {
|
||||
test: SomeInterface
|
||||
}
|
||||
|
||||
interface SomeInterface {
|
||||
foo: String
|
||||
}
|
||||
');
|
||||
$this->assertContainsValidationMessage(
|
||||
$schema->validate(),
|
||||
[[
|
||||
'message' => 'Interface SomeInterface must be implemented by at least one Object type.',
|
||||
'locations' => [[ 'line' => 6, 'column' => 7 ]],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see it('accepts an input type as a field arg type')
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user