TypesRule::getFieldArgumentNode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Schema\Validation\Rule;
4
5
use Digia\GraphQL\Error\InvariantException;
6
use Digia\GraphQL\Language\Node\ASTNodeAwareInterface;
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\ObjectTypeDefinitionNode;
14
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
15
use Digia\GraphQL\Language\Node\TypeNodeInterface;
16
use Digia\GraphQL\Language\Node\UnionTypeDefinitionNode;
17
use Digia\GraphQL\Schema\Validation\SchemaValidationException;
18
use Digia\GraphQL\Type\Definition\Argument;
19
use Digia\GraphQL\Type\Definition\EnumType;
20
use Digia\GraphQL\Type\Definition\FieldsAwareInterface;
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 Digia\GraphQL\Util\NameHelper;
28
use Digia\GraphQL\Util\TypeHelper;
29
use function Digia\GraphQL\Type\isInputType;
30
use function Digia\GraphQL\Type\isIntrospectionType;
31
use function Digia\GraphQL\Type\isOutputType;
32
use function Digia\GraphQL\Util\find;
33
use function Digia\GraphQL\Util\toString;
34
35
class TypesRule extends AbstractRule
36
{
37
    /**
38
     * @inheritdoc
39
     *
40
     * @throws InvariantException
41
     */
42
    public function evaluate(): void
43
    {
44
        $typeMap = $this->context->getSchema()->getTypeMap();
45
46
        foreach ($typeMap as $type) {
47
            if (!($type instanceof NamedTypeInterface)) {
48
                $this->context->reportError(
49
                    new SchemaValidationException(
50
                        \sprintf('Expected GraphQL named type but got: %s.', toString($type)),
51
                        $type instanceof ASTNodeAwareInterface ? [$type->getAstNode()] : null
52
                    )
53
                );
54
55
                continue;
56
            }
57
58
            // Ensure it is named correctly (excluding introspection types).
59
            /** @noinspection PhpParamsInspection */
60
            if (!isIntrospectionType($type)) {
61
                $this->validateName($type);
62
            }
63
64
            if ($type instanceof ObjectType) {
65
                // Ensure fields are valid.
66
                $this->validateFields($type);
67
                // Ensure objects implement the interfaces they claim to.
68
                $this->validateObjectInterfaces($type);
69
                continue;
70
            }
71
72
            if ($type instanceof InterfaceType) {
73
                // Ensure fields are valid.
74
                $this->validateFields($type);
75
                continue;
76
            }
77
78
            if ($type instanceof UnionType) {
79
                // Ensure Unions include valid member types.
80
                $this->validateUnionMembers($type);
81
                continue;
82
            }
83
84
            if ($type instanceof EnumType) {
85
                // Ensure Enums have valid values.
86
                $this->validateEnumValues($type);
87
                continue;
88
            }
89
90
            if ($type instanceof InputObjectType) {
91
                // Ensure Input Object fields are valid.
92
                $this->validateInputFields($type);
93
                continue;
94
            }
95
        }
96
    }
97
98
    /**
99
     * @param FieldsAwareInterface $type
100
     * @throws InvariantException
101
     */
102
    protected function validateFields(FieldsAwareInterface $type): void
103
    {
104
        $fields = $type->getFields();
105
106
        // Objects and Interfaces both must define one or more fields.
107
        if (empty($fields)) {
108
            $this->context->reportError(
109
                new SchemaValidationException(
110
                    \sprintf('Type %s must define one or more fields.', $type->getName()),
111
                    $this->getAllObjectOrInterfaceNodes($type)
112
                )
113
            );
114
        }
115
116
        foreach ($fields as $fieldName => $field) {
117
            // Ensure they are named correctly.
118
            $this->validateName($field);
119
120
            // Ensure they were defined at most once.
121
            $fieldNodes = $this->getAllFieldNodes($type, $fieldName);
122
123
            if (\count($fieldNodes) > 1) {
124
                $this->context->reportError(
125
                    new SchemaValidationException(
126
                        \sprintf('Field %s.%s can only be defined once.', $type->getName(), $fieldName),
127
                        $fieldNodes
128
                    )
129
                );
130
131
                return; // continue loop
132
            }
133
134
            $fieldType = $field->getType();
135
136
            // Ensure the type is an output type
137
            if (!isOutputType($fieldType)) {
138
                $fieldTypeNode = $this->getFieldTypeNode($type, $fieldName);
139
                $this->context->reportError(
140
                    new SchemaValidationException(
141
                        \sprintf(
142
                            'The type of %s.%s must be Output Type but got: %s.',
143
                            $type->getName(),
144
                            $fieldName,
145
                            toString($fieldType)
146
                        ),
147
                        [$fieldTypeNode]
148
                    )
149
                );
150
            }
151
152
            // Ensure the arguments are valid
153
            $argumentNames = [];
154
155
            foreach ($field->getArguments() as $argument) {
156
                $argumentName = $argument->getName();
157
158
                // Ensure they are named correctly.
159
                $this->validateName($argument);
160
161
                // Ensure they are unique per field.
162
                if (isset($argumentNames[$argumentName])) {
163
                    $this->context->reportError(
164
                        new SchemaValidationException(
165
                            \sprintf(
166
                                'Field argument %s.%s(%s:) can only be defined once.',
167
                                $type->getName(),
168
                                $field->getName(),
169
                                $argumentName
170
                            ),
171
                            $this->getAllFieldArgumentNodes($type, $fieldName, $argumentName)
172
                        )
173
                    );
174
                }
175
176
                $argumentNames[$argumentName] = true;
177
178
                // Ensure the type is an input type
179
                if (!isInputType($argument->getType())) {
180
                    $this->context->reportError(
181
                        new SchemaValidationException(
182
                            \sprintf(
183
                                'The type of %s.%s(%s:) must be Input Type but got: %s.',
184
                                $type->getName(),
185
                                $fieldName,
186
                                $argumentName,
187
                                toString($argument->getType())
188
                            ),
189
                            $this->getAllFieldArgumentNodes($type, $fieldName, $argumentName)
190
                        )
191
                    );
192
                }
193
            }
194
        }
195
    }
196
197
    /**
198
     * @param ObjectType $objectType
199
     * @throws InvariantException
200
     */
201
    protected function validateObjectInterfaces(ObjectType $objectType): void
202
    {
203
        $implementedTypeNames = [];
204
205
        foreach ($objectType->getInterfaces() as $interface) {
206
            if (!($interface instanceof InterfaceType)) {
207
                $this->context->reportError(
208
                    new SchemaValidationException(
209
                        \sprintf(
210
                            'Type %s must only implement Interface types, it cannot implement %s.',
211
                            toString($objectType),
212
                            toString($interface)
213
                        ),
214
                        null !== $interface
215
                            ? [$this->getImplementsInterfaceNode($objectType, $interface->getName())]
216
                            : null
217
                    )
218
                );
219
220
                continue;
221
            }
222
223
            $interfaceName = $interface->getName();
224
225
            if (isset($implementedTypeNames[$interfaceName])) {
226
                $this->context->reportError(
227
                    new SchemaValidationException(
228
                        \sprintf('Type %s can only implement %s once.', $objectType->getName(), $interfaceName),
229
                        $this->getAllImplementsInterfaceNodes($objectType, $interfaceName)
230
                    )
231
                );
232
233
                continue;
234
            }
235
236
            $implementedTypeNames[$interfaceName] = true;
237
238
            $this->validateObjectImplementsInterface($objectType, $interface);
239
        }
240
    }
241
242
    /**
243
     * @param ObjectType    $objectType
244
     * @param InterfaceType $interfaceType
245
     * @throws InvariantException
246
     */
247
    protected function validateObjectImplementsInterface(ObjectType $objectType, InterfaceType $interfaceType): void
248
    {
249
        $objectFields    = $objectType->getFields();
250
        $interfaceFields = $interfaceType->getFields();
251
252
        // Assert each interface field is implemented.
253
        foreach (\array_keys($interfaceFields) as $fieldName) {
254
            $interfaceField = $interfaceFields[$fieldName];
255
            $objectField    = $objectFields[$fieldName] ?? null;
256
257
            // Assert interface field exists on object.
258
            if (null === $objectField) {
259
                $this->context->reportError(
260
                    new SchemaValidationException(
261
                        \sprintf(
262
                            'Interface field %s.%s expected but %s does not provide it.',
263
                            $interfaceType->getName(),
264
                            $fieldName,
265
                            $objectType->getName()
266
                        ),
267
                        [$this->getFieldNode($interfaceType, $fieldName), $objectType->getAstNode()]
268
                    )
269
                );
270
271
                continue;
272
            }
273
274
            // Assert interface field type is satisfied by object field type, by being
275
            // a valid subtype. (covariant)
276
            if (!TypeHelper::isTypeSubtypeOf(
277
                $this->context->getSchema(), $objectField->getType(), $interfaceField->getType())) {
0 ignored issues
show
Bug introduced by
It seems like $objectField->getType() can also be of type null; however, parameter $maybeSubtype of Digia\GraphQL\Util\TypeHelper::isTypeSubtypeOf() does only seem to accept Digia\GraphQL\Type\Definition\TypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

277
                $this->context->getSchema(), /** @scrutinizer ignore-type */ $objectField->getType(), $interfaceField->getType())) {
Loading history...
Bug introduced by
It seems like $interfaceField->getType() can also be of type null; however, parameter $superType of Digia\GraphQL\Util\TypeHelper::isTypeSubtypeOf() does only seem to accept Digia\GraphQL\Type\Definition\TypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

277
                $this->context->getSchema(), $objectField->getType(), /** @scrutinizer ignore-type */ $interfaceField->getType())) {
Loading history...
278
                $this->context->reportError(
279
                    new SchemaValidationException(
280
                        \sprintf(
281
                            'Interface field %s.%s expects type %s but %s.%s is type %s.',
282
                            $interfaceType->getName(),
283
                            $fieldName,
284
                            toString($interfaceField->getType()),
285
                            $objectType->getName(),
286
                            $fieldName,
287
                            toString($objectField->getType())
288
                        ),
289
                        [
290
                            $this->getFieldTypeNode($interfaceType, $fieldName),
291
                            $this->getFieldTypeNode($objectType, $fieldName),
292
                        ]
293
                    )
294
                );
295
            }
296
297
            // Assert each interface field arg is implemented.
298
            foreach ($interfaceField->getArguments() as $interfaceArgument) {
299
                $argumentName   = $interfaceArgument->getName();
300
                $objectArgument = find($objectField->getArguments(), function (Argument $argument) use ($argumentName) {
301
                    return $argument->getName() === $argumentName;
302
                });
303
304
                // Assert interface field arg exists on object field.
305
                if (null === $objectArgument) {
306
                    $this->context->reportError(
307
                        new SchemaValidationException(
308
                            \sprintf(
309
                                'Interface field argument %s.%s(%s:) expected but %s.%s does not provide it.',
310
                                $interfaceType->getName(),
311
                                $fieldName,
312
                                $argumentName,
313
                                $objectType->getName(),
314
                                $fieldName
315
                            ),
316
                            [
317
                                $this->getFieldArgumentNode($interfaceType, $fieldName, $argumentName),
318
                                $this->getFieldNode($objectType, $fieldName),
319
                            ]
320
                        )
321
                    );
322
323
                    continue;
324
                }
325
326
                // Assert interface field arg type matches object field arg type.
327
                // (invariant)
328
                // TODO: change to contravariant?
329
                if (!TypeHelper::isEqualType($interfaceArgument->getType(), $objectArgument->getType())) {
0 ignored issues
show
Bug introduced by
It seems like $objectArgument->getType() can also be of type null; however, parameter $typeB of Digia\GraphQL\Util\TypeHelper::isEqualType() does only seem to accept Digia\GraphQL\Type\Definition\TypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

329
                if (!TypeHelper::isEqualType($interfaceArgument->getType(), /** @scrutinizer ignore-type */ $objectArgument->getType())) {
Loading history...
Bug introduced by
It seems like $interfaceArgument->getType() can also be of type null; however, parameter $typeA of Digia\GraphQL\Util\TypeHelper::isEqualType() does only seem to accept Digia\GraphQL\Type\Definition\TypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

329
                if (!TypeHelper::isEqualType(/** @scrutinizer ignore-type */ $interfaceArgument->getType(), $objectArgument->getType())) {
Loading history...
330
                    $this->context->reportError(
331
                        new SchemaValidationException(
332
                            \sprintf(
333
                                'Interface field argument %s.%s(%s:) expects type %s but %s.%s(%s:) is type %s.',
334
                                $interfaceType->getName(),
335
                                $fieldName,
336
                                $argumentName,
337
                                toString($interfaceArgument->getType()),
338
                                $objectType->getName(),
339
                                $fieldName,
340
                                $argumentName,
341
                                toString($objectArgument->getType())
342
                            ),
343
                            [
344
                                $this->getFieldArgumentTypeNode($interfaceType, $fieldName, $argumentName),
345
                                $this->getFieldArgumentTypeNode($objectType, $fieldName, $argumentName),
346
                            ]
347
                        )
348
                    );
349
350
                    continue;
351
                }
352
353
                // TODO: validate default values?
354
355
                foreach ($objectField->getArguments() as $objectArgument) {
356
                    $argumentName      = $objectArgument->getName();
357
                    $interfaceArgument = find(
358
                        $interfaceField->getArguments(),
359
                        function (Argument $argument) use ($argumentName) {
360
                            return $argument->getName() === $argumentName;
361
                        }
362
                    );
363
364
                    if (null === $interfaceArgument && $objectArgument->getType() instanceof NonNullType) {
365
                        $this->context->reportError(
366
                            new SchemaValidationException(
367
                                \sprintf(
368
                                    'Object field argument %s.%s(%s:) is of required type %s ' .
369
                                    'but is not also provided by the Interface field %s.%s.',
370
                                    $objectType->getName(),
371
                                    $fieldName,
372
                                    $argumentName,
373
                                    toString($objectArgument->getType()),
374
                                    $interfaceType->getName(),
375
                                    $fieldName
376
                                ),
377
                                [
378
                                    $this->getFieldArgumentNode($objectType, $fieldName, $argumentName),
379
                                    $this->getFieldNode($interfaceType, $fieldName),
380
                                ]
381
                            )
382
                        );
383
384
                        continue;
385
                    }
386
                }
387
            }
388
        }
389
    }
390
391
    /**
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 = (string)$memberType;
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
                        $this->getUnionMemberTypeNodes($unionType, $memberTypeName)
438
                    )
439
                );
440
            }
441
        }
442
    }
443
444
    /**
445
     * @param EnumType $enumType
446
     * @throws InvariantException
447
     */
448
    protected function validateEnumValues(EnumType $enumType): void
449
    {
450
        $enumValues = $enumType->getValues();
451
452
        if (empty($enumValues)) {
453
            $this->context->reportError(
454
                new SchemaValidationException(
455
                    \sprintf('Enum type %s must define one or more values.', $enumType->getName()),
456
                    [$enumType->getAstNode()]
457
                )
458
            );
459
        }
460
461
        foreach ($enumValues as $enumValue) {
462
            $valueName = $enumValue->getName();
463
464
            // Ensure no duplicates.
465
            $allNodes = $this->getEnumValueNodes($enumType, $valueName);
466
467
            if (null !== $allNodes && \count($allNodes) > 1) {
468
                $this->context->reportError(
469
                    new SchemaValidationException(
470
                        sprintf('Enum type %s can include value %s only once.', $enumType->getName(), $valueName),
471
                        $allNodes
472
                    )
473
                );
474
475
                continue;
476
            }
477
478
            // Ensure valid name.
479
            $this->validateName($enumValue);
480
481
            if ($valueName === 'true' || $valueName === 'false' || $valueName === 'null') {
482
                $this->context->reportError(
483
                    new SchemaValidationException(
484
                        sprintf('Enum type %s cannot include value: %s.', $enumType->getName(), $valueName),
485
                        [$enumValue->getAstNode()]
486
                    )
487
                );
488
489
                continue;
490
            }
491
        }
492
    }
493
494
    /**
495
     * @param InputObjectType $inputObjectType
496
     * @throws InvariantException
497
     */
498
    protected function validateInputFields(InputObjectType $inputObjectType): void
499
    {
500
        $fields = $inputObjectType->getFields();
501
502
        if (empty($fields)) {
503
            $this->context->reportError(
504
                new SchemaValidationException(
505
                    \sprintf('Input Object type %s must define one or more fields.', $inputObjectType->getName()),
506
                    [$inputObjectType->getAstNode()]
507
                )
508
            );
509
        }
510
511
        // Ensure the arguments are valid
512
        foreach ($fields as $fieldName => $field) {
513
            // Ensure they are named correctly.
514
            $this->validateName($field);
515
516
            // TODO: Ensure they are unique per field.
517
518
            // Ensure the type is an input type
519
            if (!isInputType($field->getType())) {
520
                $this->context->reportError(
521
                    new SchemaValidationException(
522
                        \sprintf(
523
                            'The type of %s.%s must be Input Type but got: %s.',
524
                            $inputObjectType->getName(),
525
                            $fieldName,
526
                            toString($field->getType())
527
                        ),
528
                        [$field->getAstNode()]
529
                    )
530
                );
531
            }
532
        }
533
    }
534
535
    /**
536
     * @param FieldsAwareInterface $type
537
     * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]
538
     * |InterfaceTypeExtensionNode[]
539
     */
540
    protected function getAllObjectOrInterfaceNodes(FieldsAwareInterface $type): array
541
    {
542
        $node              = $type->getAstNode();
543
        $extensionASTNodes = $type->getExtensionAstNodes();
544
545
        if (null !== $node) {
546
            return !empty($extensionASTNodes)
547
                ? \array_merge([$node], $extensionASTNodes)
548
                : [$node];
549
        }
550
551
        return $extensionASTNodes;
552
    }
553
554
    /**
555
     * @param FieldsAwareInterface $type
556
     * @param string               $fieldName
557
     * @return TypeNodeInterface|null
558
     */
559
    protected function getFieldTypeNode(FieldsAwareInterface $type, string $fieldName): ?TypeNodeInterface
560
    {
561
        $fieldNode = $this->getFieldNode($type, $fieldName);
562
        return null !== $fieldNode ? $fieldNode->getType() : null;
563
    }
564
565
    /**
566
     * @param FieldsAwareInterface $type
567
     * @param string               $fieldName
568
     * @return FieldDefinitionNode|null
569
     */
570
    protected function getFieldNode(FieldsAwareInterface $type, string $fieldName): ?FieldDefinitionNode
571
    {
572
        return $this->getAllFieldNodes($type, $fieldName)[0] ?? null;
573
    }
574
575
    /**
576
     * @param FieldsAwareInterface $type
577
     * @param string               $fieldName
578
     * @return FieldDefinitionNode[]
579
     */
580
    protected function getAllFieldNodes(FieldsAwareInterface $type, string $fieldName): array
581
    {
582
        $nodes = [];
583
584
        foreach ($this->getAllObjectOrInterfaceNodes($type) as $objectOrInterface) {
585
            foreach ($objectOrInterface->getFields() as $node) {
586
                if ($node->getNameValue() === $fieldName) {
587
                    $nodes[] = $node;
588
                }
589
            }
590
        }
591
592
        return $nodes;
593
    }
594
595
    /**
596
     * @param FieldsAwareInterface $type
597
     * @param string               $fieldName
598
     * @param string               $argumentName
599
     * @return InputValueDefinitionNode|null
600
     */
601
    protected function getFieldArgumentNode(
602
        FieldsAwareInterface $type,
603
        string $fieldName,
604
        string $argumentName
605
    ): ?InputValueDefinitionNode {
606
        return $this->getAllFieldArgumentNodes($type, $fieldName, $argumentName)[0] ?? null;
607
    }
608
609
    /**
610
     * @param FieldsAwareInterface $type
611
     * @param string               $fieldName
612
     * @param string               $argumentName
613
     * @return InputValueDefinitionNode[]
614
     */
615
    protected function getAllFieldArgumentNodes(
616
        FieldsAwareInterface $type,
617
        string $fieldName,
618
        string $argumentName
619
    ): array {
620
        $nodes = [];
621
622
        $fieldNode = $this->getFieldNode($type, $fieldName);
623
624
        if (null !== $fieldNode) {
625
            foreach ($fieldNode->getArguments() as $node) {
626
                if ($node->getNameValue() === $argumentName) {
627
                    $nodes[] = $node;
628
                }
629
            }
630
        }
631
632
        return $nodes;
633
    }
634
635
    /**
636
     * @param ObjectType $type
637
     * @param string     $interfaceName
638
     * @return NamedTypeNode|null
639
     */
640
    protected function getImplementsInterfaceNode(ObjectType $type, string $interfaceName): ?NamedTypeNode
641
    {
642
        return $this->getAllImplementsInterfaceNodes($type, $interfaceName)[0] ?? null;
643
    }
644
645
    /**
646
     * @param ObjectType $type
647
     * @param string     $interfaceName
648
     * @return NamedTypeNode[]
649
     */
650
    protected function getAllImplementsInterfaceNodes(ObjectType $type, string $interfaceName): array
651
    {
652
        $nodes = [];
653
654
        foreach ($this->getAllObjectOrInterfaceNodes($type) as $object) {
655
            foreach ($object->getInterfaces() as $node) {
656
                if ($node->getNameValue() === $interfaceName) {
657
                    $nodes[] = $node;
658
                }
659
            }
660
        }
661
662
        return $nodes;
663
    }
664
665
    /**
666
     * @param FieldsAwareInterface $type
667
     * @param string               $fieldName
668
     * @param string               $argumentName
669
     * @return TypeNodeInterface|null
670
     */
671
    protected function getFieldArgumentTypeNode(
672
        FieldsAwareInterface $type,
673
        string $fieldName,
674
        string $argumentName
675
    ): ?TypeNodeInterface {
676
        $node = $this->getFieldArgumentNode($type, $fieldName, $argumentName);
677
        return null !== $node ? $node->getType() : null;
678
    }
679
680
    /**
681
     * @param UnionType $unionType
682
     * @param string    $memberTypeName
683
     * @return array|null
684
     */
685
    protected function getUnionMemberTypeNodes(UnionType $unionType, string $memberTypeName): ?array
686
    {
687
        /** @var UnionTypeDefinitionNode|null $node */
688
        $node = $unionType->getAstNode();
689
690
        if (null === $node) {
691
            return null;
692
        }
693
694
        return \array_filter($node->getTypes(), function (NamedTypeNode $type) use ($memberTypeName) {
695
            return $type->getNameValue() === $memberTypeName;
696
        });
697
    }
698
699
    /**
700
     * @param EnumType $enumType
701
     * @param string   $valueName
702
     * @return array|null
703
     */
704
    protected function getEnumValueNodes(EnumType $enumType, string $valueName): ?array
705
    {
706
        /** @var EnumTypeDefinitionNode|null $node */
707
        $node = $enumType->getAstNode();
708
709
        if (null === $node) {
710
            return null;
711
        }
712
713
        return \array_filter($node->getValues(), function (NameAwareInterface $type) use ($valueName) {
714
            return $type->getNameValue() === $valueName;
715
        });
716
    }
717
718
    /**
719
     * @param mixed $node
720
     */
721
    protected function validateName($node): void
722
    {
723
        // Ensure names are valid, however introspection types opt out.
724
        $error = NameHelper::isValidError($node->getName(), $node);
725
726
        if (null !== $error) {
727
            $this->context->reportError($error);
728
        }
729
    }
730
}
731