Skip to content

Commit 08d9db9

Browse files
authored
Add support of SDL to KnownTypeNames validation rule (#999)
1 parent b6ac0ff commit 08d9db9

File tree

8 files changed

+106
-311
lines changed

8 files changed

+106
-311
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ You can find and compare releases at the [GitHub release page](https://github.co
5757
- Allow lazy enum values
5858
- Make `Node` implement `JsonSerializable`
5959
- Add SDL validation rule `UniqueTypeNames` (#998)
60+
- Add support for SDL validation to `KnownTypeNames` rule (#999)
6061

6162
### Optimized
6263

src/Utils/BuildSchema.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -212,26 +212,17 @@ static function (string $typeName): Type {
212212
}
213213

214214
/**
215-
* @throws Error
216-
*
217215
* @return array<string, string>
218216
*/
219217
private function getOperationTypes(SchemaDefinitionNode $schemaDef): array
220218
{
221-
$opTypes = [];
222-
219+
/** @var array<string, string> $operationTypes */
220+
$operationTypes = [];
223221
foreach ($schemaDef->operationTypes as $operationType) {
224-
$typeName = $operationType->type->name->value;
225-
$operation = $operationType->operation;
226-
227-
if (! isset($this->nodeMap[$typeName])) {
228-
throw new Error('Specified ' . $operation . ' type "' . $typeName . '" not found in document.');
229-
}
230-
231-
$opTypes[$operation] = $typeName;
222+
$operationTypes[$operationType->operation] = $operationType->type->name->value;
232223
}
233224

234-
return $opTypes;
225+
return $operationTypes;
235226
}
236227

237228
public static function unknownType(string $typeName): Error

src/Utils/SchemaExtender.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,11 @@ public static function extend(
614614
$typeDefinitionMap,
615615
static function (string $typeName) use ($schema): Type {
616616
$existingType = $schema->getType($typeName);
617-
if ($existingType !== null) {
618-
return static::extendNamedType($existingType);
617+
if ($existingType === null) {
618+
throw new InvariantViolation('Unknown type: "' . $typeName . '".');
619619
}
620620

621-
throw new Error('Unknown type: "' . $typeName . '". Ensure that this type exists either in the original schema, or is added in a type definition.');
621+
return static::extendNamedType($existingType);
622622
},
623623
$typeConfigDecorator
624624
);

src/Validator/DocumentValidator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ public static function sdlRules(): array
203203
LoneSchemaDefinition::class => new LoneSchemaDefinition(),
204204
UniqueOperationTypes::class => new UniqueOperationTypes(),
205205
UniqueTypeNames::class => new UniqueTypeNames(),
206+
KnownTypeNames::class => new KnownTypeNames(),
206207
KnownDirectives::class => new KnownDirectives(),
207208
KnownArgumentNamesOnDirectives::class => new KnownArgumentNamesOnDirectives(),
208209
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),

src/Validator/Rules/KnownTypeNames.php

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,86 @@
77
use GraphQL\Error\Error;
88
use GraphQL\Language\AST\NamedTypeNode;
99
use GraphQL\Language\AST\NodeKind;
10-
use GraphQL\Language\Visitor;
11-
use GraphQL\Language\VisitorOperation;
10+
use GraphQL\Language\AST\TypeDefinitionNode;
11+
use GraphQL\Language\AST\TypeSystemDefinitionNode;
12+
use GraphQL\Language\AST\TypeSystemExtensionNode;
13+
use GraphQL\Type\Definition\Type;
1214
use GraphQL\Utils\Utils;
1315
use GraphQL\Validator\QueryValidationContext;
16+
use GraphQL\Validator\SDLValidationContext;
17+
use GraphQL\Validator\ValidationContext;
18+
use function in_array;
1419

1520
/**
1621
* Known type names.
1722
*
1823
* A GraphQL document is only valid if referenced types (specifically
1924
* variable definitions and fragment conditions) are defined by the type schema.
25+
*
26+
* @phpstan-import-type VisitorArray from \GraphQL\Language\Visitor
2027
*/
2128
class KnownTypeNames extends ValidationRule
2229
{
2330
public function getVisitor(QueryValidationContext $context): array
2431
{
25-
$skip = static function (): VisitorOperation {
26-
return Visitor::skipNode();
27-
};
32+
return $this->getASTVisitor($context);
33+
}
34+
35+
public function getSDLVisitor(SDLValidationContext $context): array
36+
{
37+
return $this->getASTVisitor($context);
38+
}
39+
40+
/**
41+
* @phpstan-return VisitorArray
42+
*/
43+
public function getASTVisitor(ValidationContext $context): array
44+
{
45+
/** @var array<int, string> $definedTypes */
46+
$definedTypes = [];
47+
foreach ($context->getDocument()->definitions as $def) {
48+
if ($def instanceof TypeDefinitionNode) {
49+
$definedTypes[] = $def->name->value;
50+
}
51+
}
52+
53+
$standardTypeNames = array_keys(Type::getAllBuiltInTypes());
2854

2955
return [
30-
// TODO: when validating IDL, re-enable these. Experimental version does not
31-
// add unreferenced types, resulting in false-positive errors. Squelched
32-
// errors for now.
33-
NodeKind::OBJECT_TYPE_DEFINITION => $skip,
34-
NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
35-
NodeKind::UNION_TYPE_DEFINITION => $skip,
36-
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip,
37-
NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) use ($context): void {
38-
$schema = $context->getSchema();
56+
NodeKind::NAMED_TYPE => static function (NamedTypeNode $node, $_1, $parent, $_2, $ancestors) use ($context, $definedTypes, $standardTypeNames): void {
3957
$typeName = $node->name->value;
40-
$type = $schema->getType($typeName);
41-
if ($type !== null) {
58+
$schema = $context->getSchema();
59+
60+
if (in_array($typeName, $definedTypes, true)) {
61+
return;
62+
}
63+
64+
if ($schema !== null && $schema->hasType($typeName)) {
65+
return;
66+
}
67+
68+
$definitionNode = $ancestors[2] ?? $parent;
69+
$isSDL = $definitionNode instanceof TypeSystemDefinitionNode || $definitionNode instanceof TypeSystemExtensionNode;
70+
if ($isSDL && in_array($typeName, $standardTypeNames, true)) {
4271
return;
4372
}
4473

74+
$existingTypesMap = $schema !== null
75+
? $schema->getTypeMap()
76+
: [];
77+
$typeNames = [
78+
...array_keys($existingTypesMap),
79+
...$definedTypes,
80+
];
4581
$context->reportError(new Error(
4682
static::unknownTypeMessage(
4783
$typeName,
48-
Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
84+
Utils::suggestionList(
85+
$typeName,
86+
$isSDL
87+
? [...$standardTypeNames, ...$typeNames]
88+
: $typeNames
89+
)
4990
),
5091
[$node]
5192
));

tests/Utils/BuildSchemaLegacyTest.php

Lines changed: 0 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace GraphQL\Tests\Utils;
44

55
use GraphQL\Error\DebugFlag;
6-
use GraphQL\Error\Error;
76
use GraphQL\GraphQL;
87
use GraphQL\Language\Parser;
98
use GraphQL\Utils\BuildSchema;
@@ -14,7 +13,6 @@
1413
* Their counterparts have been removed from `buildASTSchema-test.js` and moved elsewhere,
1514
* but these changes to `graphql-js` haven't been reflected in `graphql-php` yet.
1615
* TODO align with:
17-
* - https://github.com/graphql/graphql-js/commit/3b9ea61f2348215dee755f779caef83df749d2bb
1816
* - https://github.com/graphql/graphql-js/commit/64a5c3448a201737f9218856786c51d66f2deabd.
1917
*/
2018
class BuildSchemaLegacyTest extends TestCase
@@ -145,177 +143,4 @@ interface Character {
145143
$result = GraphQL::executeQuery($schema, $source, $rootValue);
146144
self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE));
147145
}
148-
149-
// Describe: Failures
150-
151-
/**
152-
* @see it('Unknown type referenced')
153-
*/
154-
public function testUnknownTypeReferenced(): void
155-
{
156-
$this->expectExceptionObject(BuildSchema::unknownType('Bar'));
157-
$sdl = '
158-
schema {
159-
query: Hello
160-
}
161-
162-
type Hello {
163-
bar: Bar
164-
}
165-
';
166-
$doc = Parser::parse($sdl);
167-
$schema = BuildSchema::buildAST($doc);
168-
$schema->getTypeMap();
169-
}
170-
171-
/**
172-
* @see it('Unknown type in interface list')
173-
*/
174-
public function testUnknownTypeInInterfaceList(): void
175-
{
176-
$this->expectExceptionObject(BuildSchema::unknownType('Bar'));
177-
$sdl = '
178-
type Query implements Bar {
179-
field: String
180-
}
181-
';
182-
$doc = Parser::parse($sdl);
183-
$schema = BuildSchema::buildAST($doc);
184-
$schema->getTypeMap();
185-
}
186-
187-
/**
188-
* @see it('Unknown type in union list')
189-
*/
190-
public function testUnknownTypeInUnionList(): void
191-
{
192-
$this->expectExceptionObject(BuildSchema::unknownType('Bar'));
193-
$sdl = '
194-
union TestUnion = Bar
195-
type Query { testUnion: TestUnion }
196-
';
197-
$doc = Parser::parse($sdl);
198-
$schema = BuildSchema::buildAST($doc);
199-
$schema->getTypeMap();
200-
}
201-
202-
/**
203-
* @see it('Unknown query type')
204-
*/
205-
public function testUnknownQueryType(): void
206-
{
207-
$this->expectException(Error::class);
208-
$this->expectExceptionMessage('Specified query type "Wat" not found in document.');
209-
$sdl = '
210-
schema {
211-
query: Wat
212-
}
213-
214-
type Hello {
215-
str: String
216-
}
217-
';
218-
$doc = Parser::parse($sdl);
219-
BuildSchema::buildAST($doc);
220-
}
221-
222-
/**
223-
* @see it('Unknown mutation type')
224-
*/
225-
public function testUnknownMutationType(): void
226-
{
227-
$this->expectException(Error::class);
228-
$this->expectExceptionMessage('Specified mutation type "Wat" not found in document.');
229-
$sdl = '
230-
schema {
231-
query: Hello
232-
mutation: Wat
233-
}
234-
235-
type Hello {
236-
str: String
237-
}
238-
';
239-
$doc = Parser::parse($sdl);
240-
BuildSchema::buildAST($doc);
241-
}
242-
243-
/**
244-
* @see it('Unknown subscription type')
245-
*/
246-
public function testUnknownSubscriptionType(): void
247-
{
248-
$this->expectException(Error::class);
249-
$this->expectExceptionMessage('Specified subscription type "Awesome" not found in document.');
250-
$sdl = '
251-
schema {
252-
query: Hello
253-
mutation: Wat
254-
subscription: Awesome
255-
}
256-
257-
type Hello {
258-
str: String
259-
}
260-
261-
type Wat {
262-
str: String
263-
}
264-
';
265-
$doc = Parser::parse($sdl);
266-
BuildSchema::buildAST($doc);
267-
}
268-
269-
/**
270-
* @see it('Does not consider directive names')
271-
*/
272-
public function testDoesNotConsiderDirectiveNames(): void
273-
{
274-
$sdl = '
275-
schema {
276-
query: Foo
277-
}
278-
279-
directive @Foo on QUERY
280-
';
281-
$doc = Parser::parse($sdl);
282-
$this->expectExceptionMessage('Specified query type "Foo" not found in document.');
283-
BuildSchema::build($doc);
284-
}
285-
286-
/**
287-
* @see it('Does not consider operation names')
288-
*/
289-
public function testDoesNotConsiderOperationNames(): void
290-
{
291-
$this->expectException(Error::class);
292-
$this->expectExceptionMessage('Specified query type "Foo" not found in document.');
293-
$sdl = '
294-
schema {
295-
query: Foo
296-
}
297-
298-
query Foo { field }
299-
';
300-
$doc = Parser::parse($sdl);
301-
BuildSchema::buildAST($doc);
302-
}
303-
304-
/**
305-
* @see it('Does not consider fragment names')
306-
*/
307-
public function testDoesNotConsiderFragmentNames(): void
308-
{
309-
$this->expectException(Error::class);
310-
$this->expectExceptionMessage('Specified query type "Foo" not found in document.');
311-
$sdl = '
312-
schema {
313-
query: Foo
314-
}
315-
316-
fragment Foo on Type { field }
317-
';
318-
$doc = Parser::parse($sdl);
319-
BuildSchema::buildAST($doc);
320-
}
321146
}

0 commit comments

Comments
 (0)