Passed
Pull Request — master (#164)
by Christoffer
02:18
created

TypesRule::evaluate()   C

Complexity

Conditions 10
Paths 14

Size

Total Lines 52
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 52
rs 6.2553
c 0
b 0
f 0
cc 10
eloc 26
nc 14
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Digia\GraphQL\Schema\Validation\Rule;
4
5
use Digia\GraphQL\Error\InvariantException;
6
use Digia\GraphQL\Error\SchemaValidationException;
7
use Digia\GraphQL\Language\Node\EnumTypeDefinitionNode;
8
use Digia\GraphQL\Language\Node\FieldDefinitionNode;
9
use Digia\GraphQL\Language\Node\InputValueDefinitionNode;
10
use Digia\GraphQL\Language\Node\InterfaceTypeDefinitionNode;
11
use Digia\GraphQL\Language\Node\NameAwareInterface;
12
use Digia\GraphQL\Language\Node\NamedTypeNode;
13
use Digia\GraphQL\Language\Node\NodeAwareInterface;
14
use Digia\GraphQL\Language\Node\ObjectTypeDefinitionNode;
15
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
16
use Digia\GraphQL\Language\Node\TypeNodeInterface;
17
use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode;
18
use Digia\GraphQL\Schema\Validation\ValidationContext;
19
use Digia\GraphQL\Type\Definition\Argument;
20
use Digia\GraphQL\Type\Definition\EnumType;
21
use Digia\GraphQL\Type\Definition\InputObjectType;
22
use Digia\GraphQL\Type\Definition\InterfaceType;
23
use Digia\GraphQL\Type\Definition\NamedTypeInterface;
24
use Digia\GraphQL\Type\Definition\NonNullType;
25
use Digia\GraphQL\Type\Definition\ObjectType;
26
use Digia\GraphQL\Type\Definition\UnionType;
27
use function Digia\GraphQL\Type\isInputType;
28
use function Digia\GraphQL\Type\isIntrospectionType;
29
use function Digia\GraphQL\Type\isOutputType;
30
use function Digia\GraphQL\Util\find;
31
use function Digia\GraphQL\Util\isEqualType;
32
use function Digia\GraphQL\Util\isTypeSubtypeOf;
33
use function Digia\GraphQL\Util\isValidNameError;
34
use function Digia\GraphQL\Util\toString;
35
36
class TypesRule extends AbstractRule
37
{
38
    /**
39
     * @inheritdoc
40
     */
41
    public function evaluate(): void
42
    {
43
        $typeMap = $this->context->getSchema()->getTypeMap();
44
45
        foreach ($typeMap as $type) {
46
            if (!($type instanceof NamedTypeInterface)) {
47
                $this->context->reportError(
48
                    new SchemaValidationException(
49
                        \sprintf('Expected GraphQL named type but got: %s.', toString($type)),
50
                        $type instanceof NodeAwareInterface ? [$type->getAstNode()] : null
51
                    )
52
                );
53
54
                continue;
55
            }
56
57
            // Ensure it is named correctly (excluding introspection types).
58
            /** @noinspection PhpParamsInspection */
59
            if (!isIntrospectionType($type)) {
60
                $this->validateName($type);
61
            }
62
63
            if ($type instanceof ObjectType) {
64
                // Ensure fields are valid.
65
                $this->validateFields($type);
66
                // Ensure objects implement the interfaces they claim to.
67
                $this->validateObjectInterfaces($type);
68
                continue;
69
            }
70
71
            if ($type instanceof InterfaceType) {
72
                // Ensure fields are valid.
73
                $this->validateFields($type);
74
                continue;
75
            }
76
77
            if ($type instanceof UnionType) {
78
                // Ensure Unions include valid member types.
79
                $this->validateUnionMembers($type);
80
                continue;
81
            }
82
83
            if ($type instanceof EnumType) {
84
                // Ensure Enums have valid values.
85
                $this->validateEnumValues($type);
86
                continue;
87
            }
88
89
            if ($type instanceof InputObjectType) {
90
                // Ensure Input Object fields are valid.
91
                $this->validateInputFields($type);
92
                continue;
93
            }
94
        }
95
    }
96
97
    /**
98
     * @param NamedTypeInterface|ObjectType|InterfaceType $type
99
     * @throws InvariantException
100
     */
101
    protected function validateFields(NamedTypeInterface $type): void
102
    {
103
        $fields = $type->getFields();
0 ignored issues
show
Bug introduced by
The method getFields() does not exist on Digia\GraphQL\Type\Definition\NamedTypeInterface. It seems like you code against a sub-type of Digia\GraphQL\Type\Definition\NamedTypeInterface such as Digia\GraphQL\Type\Definition\InputObjectType or Digia\GraphQL\Type\Definition\InterfaceType or Digia\GraphQL\Type\Definition\ObjectType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

103
        /** @scrutinizer ignore-call */ 
104
        $fields = $type->getFields();
Loading history...
104
105
        // Objects and Interfaces both must define one or more fields.
106
        if (empty($fields)) {
107
            $this->context->reportError(
108
                new SchemaValidationException(
109
                    \sprintf('Type %s must define one or more fields.', $type->getName()),
110
                    $this->getAllObjectOrInterfaceNodes($type)
111
                )
112
            );
113
        }
114
115
        foreach ($fields as $fieldName => $field) {
116
            // Ensure they are named correctly.
117
            $this->validateName($field);
118
119
            // Ensure they were defined at most once.
120
            $fieldNodes = $this->getAllFieldNodes($type, $fieldName);
121
122
            if (\count($fieldNodes) > 1) {
123
                $this->context->reportError(
124
                    new SchemaValidationException(
125
                        \sprintf('Field %s.%s can only be defined once.', $type->getName(), $fieldName),
126
                        $fieldNodes
127
                    )
128
                );
129
130
                return; // continue loop
131
            }
132
133
            $fieldType = $field->getType();
134
135
            // Ensure the type is an output type
136
            if (!isOutputType($fieldType)) {
137
                $fieldTypeNode = $this->getFieldTypeNode($type, $fieldName);
138
                $this->context->reportError(
139
                    new SchemaValidationException(
140
                        \sprintf(
141
                            'The type of %s.%s must be Output Type but got: %s.',
142
                            $type->getName(),
143
                            $fieldName,
144
                            toString($fieldType)
145
                        ),
146
                        [$fieldTypeNode]
147
                    )
148
                );
149
            }
150
151
            // Ensure the arguments are valid
152
            $argumentNames = [];
153
154
            foreach ($field->getArguments() as $argument) {
155
                $argumentName = $argument->getName();
156
157
                // Ensure they are named correctly.
158
                $this->validateName($argument);
159
160
                // Ensure they are unique per field.
161
                if (isset($argumentNames[$argumentName])) {
162
                    $this->context->reportError(
163
                        new SchemaValidationException(
164
                            \sprintf(
165
                                'Field argument %s.%s(%s:) can only be defined once.',
166
                                $type->getName(),
167
                                $field->getName(),
168
                                $argumentName
169
                            ),
170
                            $this->getAllFieldArgumentNodes($type, $fieldName, $argumentName)
171
                        )
172
                    );
173
                }
174
175
                $argumentNames[$argumentName] = true;
176
177
                // Ensure the type is an input type
178
                if (!isInputType($argument->getType())) {
179
                    $this->context->reportError(
180
                        new SchemaValidationException(
181
                            \sprintf(
182
                                'The type of %s.%s(%s:) must be Input Type but got: %s.',
183
                                $type->getName(),
184
                                $fieldName,
185
                                $argumentName,
186
                                toString($argument->getType())
187
                            ),
188
                            $this->getAllFieldArgumentNodes($type, $fieldName, $argumentName)
189
                        )
190
                    );
191
                }
192
            }
193
        }
194
    }
195
196
    /**
197
     * @param ObjectType $objectType
198
     * @throws InvariantException
199
     */
200
    protected function validateObjectInterfaces(ObjectType $objectType): void
201
    {
202
        $implementedTypeNames = [];
203
204
        foreach ($objectType->getInterfaces() as $interface) {
205
            if (!($interface instanceof InterfaceType)) {
206
                $this->context->reportError(
207
                    new SchemaValidationException(
208
                        \sprintf(
209
                            'Type %s must only implement Interface types, it cannot implement %s.',
210
                            toString($objectType),
211
                            toString($interface)
212
                        ),
213
                        null !== $interface
214
                            ? [$this->getImplementsInterfaceNode($objectType, $interface->getName())]
215
                            : null
216
                    )
217
                );
218
219
                continue;
220
            }
221
222
            $interfaceName = $interface->getName();
223
224
            if (isset($implementedTypeNames[$interfaceName])) {
225
                $this->context->reportError(
226
                    new SchemaValidationException(
227
                        \sprintf('Type %s can only implement %s once.', $objectType->getName(), $interfaceName),
228
                        $this->getAllImplementsInterfaceNodes($objectType, $interfaceName)
229
                    )
230
                );
231
232
                continue;
233
            }
234
235
            $implementedTypeNames[$interfaceName] = true;
236
237
            $this->validateObjectImplementsInterface($objectType, $interface);
238
        }
239
    }
240
241
    /**
242
     * @param ObjectType    $objectType
243
     * @param InterfaceType $interfaceType
244
     * @throws InvariantException
245
     */
246
    protected function validateObjectImplementsInterface(ObjectType $objectType, InterfaceType $interfaceType): void
247
    {
248
        $objectFields    = $objectType->getFields();
249
        $interfaceFields = $interfaceType->getFields();
250
251
        // Assert each interface field is implemented.
252
        foreach (\array_keys($interfaceFields) as $fieldName) {
253
            $interfaceField = $interfaceFields[$fieldName];
254
            $objectField    = $objectFields[$fieldName] ?? null;
255
256
            // Assert interface field exists on object.
257
            if (null === $objectField) {
258
                $this->context->reportError(
259
                    new SchemaValidationException(
260
                        \sprintf(
261
                            'Interface field %s.%s expected but %s does not provide it.',
262
                            $interfaceType->getName(),
263
                            $fieldName,
264
                            $objectType->getName()
265
                        ),
266
                        [$this->getFieldNode($interfaceType, $fieldName), $objectType->getAstNode()]
267
                    )
268
                );
269
270
                continue;
271
            }
272
273
            // Assert interface field type is satisfied by object field type, by being
274
            // a valid subtype. (covariant)
275
            if (!isTypeSubtypeOf(
276
                $this->context->getSchema(), $objectField->getType(), $interfaceField->getType())) {
277
                $this->context->reportError(
278
                    new SchemaValidationException(
279
                        \sprintf(
280
                            'Interface field %s.%s expects type %s but %s.%s is type %s.',
281
                            $interfaceType->getName(),
282
                            $fieldName,
283
                            toString($interfaceField->getType()),
284
                            $objectType->getName(),
285
                            $fieldName,
286
                            toString($objectField->getType())
287
                        ),
288
                        [
289
                            $this->getFieldTypeNode($interfaceType, $fieldName),
290
                            $this->getFieldTypeNode($objectType, $fieldName),
291
                        ]
292
                    )
293
                );
294
            }
295
296
            // Assert each interface field arg is implemented.
297
            foreach ($interfaceField->getArguments() as $interfaceArgument) {
298
                $argumentName   = $interfaceArgument->getName();
299
                $objectArgument = find($objectField->getArguments(), function (Argument $argument) use ($argumentName) {
300
                    return $argument->getName() === $argumentName;
301
                });
302
303
                // Assert interface field arg exists on object field.
304
                if (null === $objectArgument) {
305
                    $this->context->reportError(
306
                        new SchemaValidationException(
307
                            \sprintf(
308
                                'Interface field argument %s.%s(%s:) expected but %s.%s does not provide it.',
309
                                $interfaceType->getName(),
310
                                $fieldName,
311
                                $argumentName,
312
                                $objectType->getName(),
313
                                $fieldName
314
                            ),
315
                            [
316
                                $this->getFieldArgumentNode($interfaceType, $fieldName, $argumentName),
317
                                $this->getFieldNode($objectType, $fieldName),
318
                            ]
319
                        )
320
                    );
321
322
                    continue;
323
                }
324
325
                // Assert interface field arg type matches object field arg type.
326
                // (invariant)
327
                // TODO: change to contravariant?
328
                if (!isEqualType($interfaceArgument->getType(), $objectArgument->getType())) {
329
                    $this->context->reportError(
330
                        new SchemaValidationException(
331
                            \sprintf(
332
                                'Interface field argument %s.%s(%s:) expects type %s but %s.%s(%s:) is type %s.',
333
                                $interfaceType->getName(),
334
                                $fieldName,
335
                                $argumentName,
336
                                toString($interfaceArgument->getType()),
337
                                $objectType->getName(),
338
                                $fieldName,
339
                                $argumentName,
340
                                toString($objectArgument->getType())
341
                            ),
342
                            [
343
                                $this->getFieldArgumentTypeNode($interfaceType, $fieldName, $argumentName),
344
                                $this->getFieldArgumentTypeNode($objectType, $fieldName, $argumentName),
345
                            ]
346
                        )
347
                    );
348
349
                    continue;
350
                }
351
352
                // TODO: validate default values?
353
354
                foreach ($objectField->getArguments() as $objectArgument) {
355
                    $argumentName      = $objectArgument->getName();
356
                    $interfaceArgument = find(
357
                        $interfaceField->getArguments(),
358
                        function (Argument $argument) use ($argumentName) {
359
                            return $argument->getName() === $argumentName;
360
                        }
361
                    );
362
363
                    if (null === $interfaceArgument && $objectArgument->getType() instanceof NonNullType) {
364
                        $this->context->reportError(
365
                            new SchemaValidationException(
366
                                \sprintf(
367
                                    'Object field argument %s.%s(%s:) is of required type %s ' .
368
                                    'but is not also provided by the Interface field %s.%s.',
369
                                    $objectType->getName(),
370
                                    $fieldName,
371
                                    $argumentName,
372
                                    toString($objectArgument->getType()),
373
                                    $interfaceType->getName(),
374
                                    $fieldName
375
                                ),
376
                                [
377
                                    $this->getFieldArgumentNode($objectType, $fieldName, $argumentName),
378
                                    $this->getFieldNode($interfaceType, $fieldName),
379
                                ]
380
                            )
381
                        );
382
383
                        continue;
384
                    }
385
                }
386
            }
387
        }
388
    }
389
390
    /**
391
     * @param ValidationContext $this ->context
392
     * @param UnionType         $unionType
393
     * @throws InvariantException
394
     */
395
    protected function validateUnionMembers(UnionType $unionType): void
396
    {
397
        $memberTypes = $unionType->getTypes();
398
399
        if (empty($memberTypes)) {
400
            $this->context->reportError(
401
                new SchemaValidationException(
402
                    sprintf('Union type %s must define one or more member types.', $unionType->getName()),
403
                    [$unionType->getAstNode()]
404
                )
405
            );
406
        }
407
408
        $includedTypeNames = [];
409
410
        foreach ($memberTypes as $memberType) {
411
            $memberTypeName = $memberType->getName();
412
            if (isset($includedTypeNames[$memberTypeName])) {
413
                $this->context->reportError(
414
                    new SchemaValidationException(
415
                        \sprintf(
416
                            'Union type %s can only include type %s once.',
417
                            $unionType->getName(),
418
                            $memberTypeName
419
                        ),
420
                        $this->getUnionMemberTypeNodes($unionType, $memberTypeName)
421
                    )
422
                );
423
424
                continue;
425
            }
426
427
            $includedTypeNames[$memberTypeName] = true;
428
429
            if (!($memberType instanceof ObjectType)) {
430
                $this->context->reportError(
431
                    new SchemaValidationException(
432
                        \sprintf(
433
                            'Union type %s can only include Object types, it cannot include %s.',
434
                            $unionType->getName(),
435
                            toString($memberType)
436
                        ),
437
                        null !== $memberTypeName
438
                            ? $this->getUnionMemberTypeNodes($unionType, $memberTypeName)
439
                            : null
440
                    )
441
                );
442
            }
443
        }
444
    }
445
446
    /**
447
     * @param ValidationContext $this ->context
448
     * @param EnumType          $enumType
449
     * @throws InvariantException
450
     */
451
    protected function validateEnumValues(EnumType $enumType): void
452
    {
453
        $enumValues = $enumType->getValues();
454
455
        if (empty($enumValues)) {
456
            $this->context->reportError(
457
                new SchemaValidationException(
458
                    \sprintf('Enum type %s must define one or more values.', $enumType->getName()),
459
                    [$enumType->getAstNode()]
460
                )
461
            );
462
        }
463
464
        foreach ($enumValues as $enumValue) {
465
            $valueName = $enumValue->getName();
466
467
            // Ensure no duplicates.
468
            $allNodes = $this->getEnumValueNodes($enumType, $valueName);
469
470
            if (null !== $allNodes && \count($allNodes) > 1) {
471
                $this->context->reportError(
472
                    new SchemaValidationException(
473
                        sprintf('Enum type %s can include value %s only once.', $enumType->getName(), $valueName),
474
                        $allNodes
475
                    )
476
                );
477
478
                continue;
479
            }
480
481
            // Ensure valid name.
482
            $this->validateName($enumValue);
483
484
            if ($valueName === 'true' || $valueName === 'false' || $valueName === 'null') {
485
                $this->context->reportError(
486
                    new SchemaValidationException(
487
                        sprintf('Enum type %s cannot include value: %s.', $enumType->getName(), $valueName),
488
                        [$enumValue->getAstNode()]
489
                    )
490
                );
491
492
                continue;
493
            }
494
        }
495
    }
496
497
    /**
498
     * @param ValidationContext $this ->context
499
     * @param InputObjectType   $inputObjectType
500
     * @throws InvariantException
501
     */
502
    protected function validateInputFields(InputObjectType $inputObjectType): void
503
    {
504
        $fields = $inputObjectType->getFields();
505
506
        if (empty($fields)) {
507
            $this->context->reportError(
508
                new SchemaValidationException(
509
                    \sprintf('Input Object type %s must define one or more fields.', $inputObjectType->getName()),
510
                    [$inputObjectType->getAstNode()]
511
                )
512
            );
513
        }
514
515
        // Ensure the arguments are valid
516
        foreach ($fields as $fieldName => $field) {
517
            // Ensure they are named correctly.
518
            $this->validateName($field);
519
520
            // TODO: Ensure they are unique per field.
521
522
            // Ensure the type is an input type
523
            if (!isInputType($field->getType())) {
524
                $this->context->reportError(
525
                    new SchemaValidationException(
526
                        \sprintf(
527
                            'The type of %s.%s must be Input Type but got: %s.',
528
                            $inputObjectType->getName(),
529
                            $fieldName,
530
                            toString($field->getType())
531
                        ),
532
                        [$field->getAstNode()]
533
                    )
534
                );
535
            }
536
        }
537
    }
538
539
    /**
540
     * @param NamedTypeInterface|ObjectType|InterfaceType $type
541
     * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]
542
     * |InterfaceTypeExtensionNode[]
543
     */
544
    protected function getAllObjectOrInterfaceNodes(NamedTypeInterface $type): array
545
    {
546
        $node = $type->getAstNode();
0 ignored issues
show
Bug introduced by
The method getAstNode() does not exist on Digia\GraphQL\Type\Definition\NamedTypeInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Digia\GraphQL\Type\Definition\NamedTypeInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

546
        /** @scrutinizer ignore-call */ 
547
        $node = $type->getAstNode();
Loading history...
547
548
        if (null !== $node) {
549
            return $type->hasExtensionAstNodes()
0 ignored issues
show
Bug introduced by
The method hasExtensionAstNodes() does not exist on Digia\GraphQL\Type\Definition\NamedTypeInterface. It seems like you code against a sub-type of Digia\GraphQL\Type\Definition\NamedTypeInterface such as Digia\GraphQL\Type\Definition\InterfaceType or Digia\GraphQL\Type\Definition\ObjectType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

549
            return $type->/** @scrutinizer ignore-call */ hasExtensionAstNodes()
Loading history...
550
                ? \array_merge([$node], $type->getExtensionAstNodes())
0 ignored issues
show
Bug introduced by
The method getExtensionAstNodes() does not exist on Digia\GraphQL\Type\Definition\NamedTypeInterface. It seems like you code against a sub-type of Digia\GraphQL\Type\Definition\NamedTypeInterface such as Digia\GraphQL\Type\Definition\InterfaceType or Digia\GraphQL\Type\Definition\ObjectType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

550
                ? \array_merge([$node], $type->/** @scrutinizer ignore-call */ getExtensionAstNodes())
Loading history...
551
                : [$node];
552
        }
553
554
        return $type->hasExtensionAstNodes() ? $type->getExtensionAstNodes() : [];
555
    }
556
557
    /**
558
     * @param NamedTypeInterface|ObjectType|InterfaceType $type
559
     * @param string                                      $fieldName
560
     * @return TypeNodeInterface|null
561
     */
562
    protected function getFieldTypeNode(NamedTypeInterface $type, string $fieldName): ?TypeNodeInterface
563
    {
564
        $fieldNode = $this->getFieldNode($type, $fieldName);
565
        return null !== $fieldNode ? $fieldNode->getType() : null;
566
    }
567
568
    /**
569
     * @param NamedTypeInterface $type
570
     * @param string             $fieldName
571
     * @return FieldDefinitionNode|null
572
     */
573
    protected function getFieldNode(NamedTypeInterface $type, string $fieldName): ?FieldDefinitionNode
574
    {
575
        return $this->getAllFieldNodes($type, $fieldName)[0] ?? null;
576
    }
577
578
    /**
579
     * @param NamedTypeInterface|ObjectType|InterfaceType $type
580
     * @param string                                      $fieldName
581
     * @return FieldDefinitionNode[]
582
     */
583
    protected function getAllFieldNodes(NamedTypeInterface $type, string $fieldName): array
584
    {
585
        $nodes = [];
586
587
        foreach ($this->getAllObjectOrInterfaceNodes($type) as $objectOrInterface) {
588
            foreach ($objectOrInterface->getFields() as $node) {
589
                if ($node->getNameValue() === $fieldName) {
590
                    $nodes[] = $node;
591
                }
592
            }
593
        }
594
595
        return $nodes;
596
    }
597
598
    /**
599
     * @param NamedTypeInterface $type
600
     * @param string             $fieldName
601
     * @param string             $argumentName
602
     * @return InputValueDefinitionNode|null
603
     */
604
    protected function getFieldArgumentNode(
605
        NamedTypeInterface $type,
606
        string $fieldName,
607
        string $argumentName
608
    ): ?InputValueDefinitionNode {
609
        return $this->getAllFieldArgumentNodes($type, $fieldName, $argumentName)[0] ?? null;
610
    }
611
612
    /**
613
     * @param NamedTypeInterface|ObjectType|InterfaceType $type
614
     * @param string                                      $fieldName
615
     * @param string                                      $argumentName
616
     * @return InputValueDefinitionNode[]
617
     */
618
    protected function getAllFieldArgumentNodes(
619
        NamedTypeInterface $type,
620
        string $fieldName,
621
        string $argumentName
622
    ): array {
623
        $nodes = [];
624
625
        $fieldNode = $this->getFieldNode($type, $fieldName);
626
627
        if (null !== $fieldNode) {
628
            foreach ($fieldNode->getArguments() as $node) {
629
                if ($node->getNameValue() === $argumentName) {
630
                    $nodes[] = $node;
631
                }
632
            }
633
        }
634
635
        return $nodes;
636
    }
637
638
    /**
639
     * @param ObjectType $type
640
     * @param string     $interfaceName
641
     * @return NamedTypeNode|null
642
     */
643
    protected function getImplementsInterfaceNode(ObjectType $type, string $interfaceName): ?NamedTypeNode
644
    {
645
        return $this->getAllImplementsInterfaceNodes($type, $interfaceName)[0] ?? null;
646
    }
647
648
    /**
649
     * @param ObjectType $type
650
     * @param string     $interfaceName
651
     * @return NamedTypeNode[]
652
     */
653
    protected function getAllImplementsInterfaceNodes(ObjectType $type, string $interfaceName): array
654
    {
655
        $nodes = [];
656
657
        foreach ($this->getAllObjectOrInterfaceNodes($type) as $object) {
658
            foreach ($object->getInterfaces() as $node) {
659
                if ($node->getNameValue() === $interfaceName) {
660
                    $nodes[] = $node;
661
                }
662
            }
663
        }
664
665
        return $nodes;
666
    }
667
668
    /**
669
     * @param NamedTypeInterface $type
670
     * @param string             $fieldName
671
     * @param string             $argumentName
672
     * @return TypeNodeInterface|null
673
     */
674
    protected function getFieldArgumentTypeNode(
675
        NamedTypeInterface $type,
676
        string $fieldName,
677
        string $argumentName
678
    ): ?TypeNodeInterface {
679
        $node = $this->getFieldArgumentNode($type, $fieldName, $argumentName);
680
        return null !== $node ? $node->getType() : null;
681
    }
682
683
    /**
684
     * @param UnionType $unionType
685
     * @param string    $memberTypeName
686
     * @return array|null
687
     */
688
    protected function getUnionMemberTypeNodes(UnionType $unionType, string $memberTypeName): ?array
689
    {
690
        /** @var UnionTypeDefinitionNode $node */
691
        $node = $unionType->getAstNode();
692
693
        if (null === $node) {
694
            return null;
695
        }
696
697
        return \array_filter($node->getTypes(), function (NamedTypeNode $type) use ($memberTypeName) {
698
            return $type->getNameValue() === $memberTypeName;
699
        });
700
    }
701
702
    /**
703
     * @param EnumType $enumType
704
     * @param string   $valueName
705
     * @return array|null
706
     */
707
    protected function getEnumValueNodes(EnumType $enumType, string $valueName): ?array
708
    {
709
        /** @var EnumTypeDefinitionNode $node */
710
        $node = $enumType->getAstNode();
711
712
        if (null === $node) {
713
            return null;
714
        }
715
716
        return \array_filter($node->getValues(), function (NameAwareInterface $type) use ($valueName) {
717
            return $type->getNameValue() === $valueName;
718
        });
719
    }
720
721
    /**
722
     * @param mixed $node
723
     * @throws InvariantException
724
     */
725
    protected function validateName($node): void
726
    {
727
        // Ensure names are valid, however introspection types opt out.
728
        $error = isValidNameError($node->getName(), $node);
729
730
        if (null !== $error) {
731
            $this->context->reportError($error);
732
        }
733
    }
734
}
735