mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-29 00:25:17 +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) {
|
} elseif ($type instanceof InterfaceType) {
|
||||||
// Ensure fields are valid.
|
// Ensure fields are valid.
|
||||||
$this->validateFields($type);
|
$this->validateFields($type);
|
||||||
|
|
||||||
|
// Ensure Interfaces include at least 1 Object type.
|
||||||
|
$this->validateInterfaces($type);
|
||||||
} elseif ($type instanceof UnionType) {
|
} elseif ($type instanceof UnionType) {
|
||||||
// Ensure Unions include valid member types.
|
// Ensure Unions include valid member types.
|
||||||
$this->validateUnionMembers($type);
|
$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
|
* @param InterfaceType $iface
|
||||||
*
|
*
|
||||||
|
@ -21,8 +21,8 @@ use GraphQL\Utils\Utils;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
use function count;
|
|
||||||
use function implode;
|
use function implode;
|
||||||
|
use function print_r;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
class ValidationTest extends TestCase
|
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([
|
$this->SomeObjectType = new ObjectType([
|
||||||
'name' => 'SomeObject',
|
'name' => 'SomeObject',
|
||||||
'fields' => ['f' => ['type' => Type::string()]],
|
'fields' => ['f' => ['type' => Type::string()]],
|
||||||
@ -87,11 +92,6 @@ class ValidationTest extends TestCase
|
|||||||
'types' => [$this->SomeObjectType],
|
'types' => [$this->SomeObjectType],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->SomeInterfaceType = new InterfaceType([
|
|
||||||
'name' => 'SomeInterface',
|
|
||||||
'fields' => ['f' => ['type' => Type::string()]],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->SomeEnumType = new EnumType([
|
$this->SomeEnumType = new EnumType([
|
||||||
'name' => 'SomeEnum',
|
'name' => 'SomeEnum',
|
||||||
'values' => [
|
'values' => [
|
||||||
@ -339,34 +339,40 @@ class ValidationTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
private function assertContainsValidationMessage($array, $messages)
|
private function assertContainsValidationMessage($array, $messages)
|
||||||
{
|
{
|
||||||
self::assertCount(
|
$allErrors = implode(
|
||||||
count($messages),
|
"\n",
|
||||||
$array,
|
array_map(
|
||||||
sprintf('For messages: %s', $messages[0]['message']) . "\n" .
|
static function ($error) {
|
||||||
"Received: \n" .
|
return $error->getMessage();
|
||||||
implode(
|
},
|
||||||
"\n",
|
$array
|
||||||
array_map(
|
|
||||||
static function ($error) {
|
|
||||||
return $error->getMessage();
|
|
||||||
},
|
|
||||||
$array
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
foreach ($array as $index => $error) {
|
|
||||||
if (! isset($messages[$index]) || ! $error instanceof Error) {
|
foreach ($messages as $expected) {
|
||||||
self::fail('Received unexpected error: ' . $error->getMessage());
|
$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());
|
self::fail(sprintf("Expected error not found:\n%s\n\nActual errors:\n%s", $msg, $allErrors));
|
||||||
$errorLocations = [];
|
|
||||||
foreach ($error->getLocations() as $location) {
|
|
||||||
$errorLocations[] = $location->toArray();
|
|
||||||
}
|
|
||||||
self::assertEquals(
|
|
||||||
$messages[$index]['locations'] ?? [],
|
|
||||||
$errorLocations
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,12 +604,18 @@ class ValidationTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
private function schemaWithFieldType($type) : Schema
|
private function schemaWithFieldType($type) : Schema
|
||||||
{
|
{
|
||||||
|
$ifaceImplementation = new ObjectType([
|
||||||
|
'name' => 'SomeInterfaceImplementation',
|
||||||
|
'fields' => ['f' => ['type' => Type::string()]],
|
||||||
|
'interfaces' => [ $this->SomeInterfaceType ],
|
||||||
|
]);
|
||||||
|
|
||||||
return new Schema([
|
return new Schema([
|
||||||
'query' => new ObjectType([
|
'query' => new ObjectType([
|
||||||
'name' => 'Query',
|
'name' => 'Query',
|
||||||
'fields' => ['f' => ['type' => $type]],
|
'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([
|
return new Schema([
|
||||||
'query' => new ObjectType([
|
'query' => new ObjectType([
|
||||||
'name' => 'Query',
|
'name' => 'Query',
|
||||||
@ -1244,6 +1264,7 @@ class ValidationTest extends TestCase
|
|||||||
'f' => ['type' => $BadInterfaceType],
|
'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')
|
* @see it('accepts an input type as a field arg type')
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user