Skip to content

Commit c0bba97

Browse files
committed
Extract check for unique type names into separate rule
graphql/graphql-js@257797a
1 parent 647e2cf commit c0bba97

File tree

7 files changed

+278
-123
lines changed

7 files changed

+278
-123
lines changed

src/Utils/BuildSchema.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,7 @@ public function buildSchema(): Schema
136136
$schemaDef = $definition;
137137
break;
138138
case $definition instanceof TypeDefinitionNode:
139-
$typeName = $definition->name->value;
140-
if (isset($this->nodeMap[$typeName])) {
141-
throw new Error('Type "' . $typeName . '" was defined more than once.');
142-
}
143-
139+
$typeName = $definition->name->value;
144140
$this->nodeMap[$typeName] = $definition;
145141
break;
146142
case $definition instanceof DirectiveDefinitionNode:

src/Utils/SchemaExtender.php

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -551,20 +551,7 @@ public static function extend(
551551
} elseif ($def instanceof SchemaTypeExtensionNode) {
552552
$schemaExtensions[] = $def;
553553
} elseif ($def instanceof TypeDefinitionNode) {
554-
$typeName = isset($def->name)
555-
? $def->name->value
556-
: null;
557-
558-
try {
559-
$type = $schema->getType($typeName);
560-
} catch (Error $error) {
561-
$type = null;
562-
}
563-
564-
if ($type !== null) {
565-
throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]);
566-
}
567-
554+
$typeName = $def->name->value;
568555
$typeDefinitionMap[$typeName] = $def;
569556
} elseif ($def instanceof TypeExtensionNode) {
570557
$extendedTypeName = $def->name->value;

src/Validator/DocumentValidator.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use GraphQL\Validator\Rules\UniqueFragmentNames;
4040
use GraphQL\Validator\Rules\UniqueInputFieldNames;
4141
use GraphQL\Validator\Rules\UniqueOperationNames;
42+
use GraphQL\Validator\Rules\UniqueTypeNames;
4243
use GraphQL\Validator\Rules\UniqueVariableNames;
4344
use GraphQL\Validator\Rules\ValidationRule;
4445
use GraphQL\Validator\Rules\ValuesOfCorrectType;
@@ -197,6 +198,7 @@ public static function sdlRules()
197198
if (self::$sdlRules === null) {
198199
self::$sdlRules = [
199200
LoneSchemaDefinition::class => new LoneSchemaDefinition(),
201+
UniqueTypeNames::class => new UniqueTypeNames(),
200202
KnownDirectives::class => new KnownDirectives(),
201203
KnownArgumentNamesOnDirectives::class => new KnownArgumentNamesOnDirectives(),
202204
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Validator\Rules;
6+
7+
use GraphQL\Error\Error;
8+
use GraphQL\Language\AST\NameNode;
9+
use GraphQL\Language\AST\NodeKind;
10+
use GraphQL\Language\Visitor;
11+
use GraphQL\Language\VisitorOperation;
12+
use GraphQL\Validator\SDLValidationContext;
13+
14+
use function array_key_exists;
15+
16+
/**
17+
* Unique type names
18+
*
19+
* A GraphQL document is only valid if all defined types have unique names.
20+
*/
21+
class UniqueTypeNames extends ValidationRule
22+
{
23+
public function getSDLVisitor(SDLValidationContext $context): array
24+
{
25+
$schema = $context->getSchema();
26+
/** @var array<string, NameNode> $knownTypeNames */
27+
$knownTypeNames = [];
28+
$checkTypeName = static function ($node) use ($context, $schema, &$knownTypeNames): ?VisitorOperation {
29+
$typeName = $node->name->value;
30+
31+
if ($schema !== null && $schema->getType($typeName) !== null) {
32+
$context->reportError(
33+
new Error(
34+
'Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.',
35+
$node->name,
36+
),
37+
);
38+
return null;
39+
}
40+
41+
if (array_key_exists($typeName, $knownTypeNames)) {
42+
$context->reportError(
43+
new Error(
44+
'There can be only one type named "' . $typeName . '".',
45+
[
46+
$knownTypeNames[$typeName],
47+
$node->name,
48+
]
49+
),
50+
);
51+
} else {
52+
$knownTypeNames[$typeName] = $node->name;
53+
}
54+
55+
return Visitor::skipNode();
56+
};
57+
58+
return [
59+
NodeKind::SCALAR_TYPE_DEFINITION => $checkTypeName,
60+
NodeKind::OBJECT_TYPE_DEFINITION => $checkTypeName,
61+
NodeKind::INTERFACE_TYPE_DEFINITION => $checkTypeName,
62+
NodeKind::UNION_TYPE_DEFINITION => $checkTypeName,
63+
NodeKind::ENUM_TYPE_DEFINITION => $checkTypeName,
64+
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $checkTypeName,
65+
];
66+
}
67+
}

tests/Utils/BuildSchemaLegacyTest.php

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
* Their counterparts have been removed from `buildASTSchema-test.js` and moved elsewhere,
1717
* but these changes to `graphql-js` haven't been reflected in `graphql-php` yet.
1818
* TODO align with:
19-
* - https://github.com/graphql/graphql-js/commit/257797a0ebdddd3da6e75b7c237fdc12a1a7c75a
2019
* - https://github.com/graphql/graphql-js/commit/9b7a8af43fc0865a01df5b5a084f37bbb8680ef8
2120
* - https://github.com/graphql/graphql-js/commit/3b9ea61f2348215dee755f779caef83df749d2bb
2221
* - https://github.com/graphql/graphql-js/commit/64a5c3448a201737f9218856786c51d66f2deabd
@@ -399,28 +398,4 @@ public function testDoesNotConsiderFragmentNames(): void
399398
$doc = Parser::parse($sdl);
400399
BuildSchema::buildAST($doc);
401400
}
402-
403-
/**
404-
* @see it('Forbids duplicate type definitions')
405-
*/
406-
public function testForbidsDuplicateTypeDefinitions(): void
407-
{
408-
$sdl = '
409-
schema {
410-
query: Repeated
411-
}
412-
413-
type Repeated {
414-
id: Int
415-
}
416-
417-
type Repeated {
418-
id: String
419-
}
420-
';
421-
$doc = Parser::parse($sdl);
422-
$this->expectException(Error::class);
423-
$this->expectExceptionMessage('Type "Repeated" was defined more than once.');
424-
BuildSchema::buildAST($doc);
425-
}
426401
}

tests/Utils/SchemaExtenderLegacyTest.php

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
* but these changes to `graphql-js` haven't been reflected in `graphql-php` yet.
3434
* TODO align with:
3535
* - https://github.com/graphql/graphql-js/commit/c1745228b2ae5ec89b8de36ea766d544607e21ea
36-
* - https://github.com/graphql/graphql-js/commit/257797a0ebdddd3da6e75b7c237fdc12a1a7c75a
3736
* - https://github.com/graphql/graphql-js/commit/3b9ea61f2348215dee755f779caef83df749d2bb
3837
* - https://github.com/graphql/graphql-js/commit/e6a3f08cc92594f68a6e61d3d4b46a6d279f845e
3938
* - https://github.com/graphql/graphql-js/commit/9b7a8af43fc0865a01df5b5a084f37bbb8680ef8
@@ -272,84 +271,6 @@ public function testDoesNotAllowReplacingAnExistingField(): void
272271
}
273272
}
274273

275-
// Extract check for unique type names into separate rule
276-
277-
/**
278-
* @see it('does not allow replacing an existing type')
279-
*/
280-
public function testDoesNotAllowReplacingAnExistingType(): void
281-
{
282-
$existingTypeError = static function ($type): string {
283-
return 'Type "' . $type . '" already exists in the schema. It cannot also be defined in this type definition.';
284-
};
285-
286-
$typeSDL = '
287-
type Bar
288-
';
289-
290-
try {
291-
$this->extendTestSchema($typeSDL);
292-
self::fail();
293-
} catch (Error $error) {
294-
self::assertEquals($existingTypeError('Bar'), $error->getMessage());
295-
}
296-
297-
$scalarSDL = '
298-
scalar SomeScalar
299-
';
300-
301-
try {
302-
$this->extendTestSchema($scalarSDL);
303-
self::fail();
304-
} catch (Error $error) {
305-
self::assertEquals($existingTypeError('SomeScalar'), $error->getMessage());
306-
}
307-
308-
$interfaceSDL = '
309-
interface SomeInterface
310-
';
311-
312-
try {
313-
$this->extendTestSchema($interfaceSDL);
314-
self::fail();
315-
} catch (Error $error) {
316-
self::assertEquals($existingTypeError('SomeInterface'), $error->getMessage());
317-
}
318-
319-
$enumSDL = '
320-
enum SomeEnum
321-
';
322-
323-
try {
324-
$this->extendTestSchema($enumSDL);
325-
self::fail();
326-
} catch (Error $error) {
327-
self::assertEquals($existingTypeError('SomeEnum'), $error->getMessage());
328-
}
329-
330-
$unionSDL = '
331-
union SomeUnion
332-
';
333-
334-
try {
335-
$this->extendTestSchema($unionSDL);
336-
self::fail();
337-
} catch (Error $error) {
338-
self::assertEquals($existingTypeError('SomeUnion'), $error->getMessage());
339-
}
340-
341-
$inputSDL = '
342-
input SomeInput
343-
';
344-
345-
try {
346-
$this->extendTestSchema($inputSDL);
347-
self::fail();
348-
} catch (Error $error) {
349-
self::assertEquals($existingTypeError('SomeInput'), $error->getMessage());
350-
}
351-
}
352-
353274
// Validation: add support of SDL to KnownTypeNames
354275

355276
/**

0 commit comments

Comments
 (0)