Completed
Pull Request — master (#45)
by Christoffer
02:04
created

getSuggestedTypeNames()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 38
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 3
nop 3
dl 0
loc 38
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Validation\Rule;
4
5
use Digia\GraphQL\Error\ValidationException;
6
use Digia\GraphQL\Language\AST\Node\FieldNode;
7
use Digia\GraphQL\Language\AST\Node\InterfacesTrait;
8
use Digia\GraphQL\Language\AST\Node\NodeInterface;
9
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
10
use Digia\GraphQL\Type\Definition\FieldsTrait;
11
use Digia\GraphQL\Type\Definition\InterfaceType;
12
use Digia\GraphQL\Type\Definition\NameTrait;
13
use Digia\GraphQL\Type\Definition\ObjectType;
14
use Digia\GraphQL\Type\Definition\OutputTypeInterface;
15
use Digia\GraphQL\Type\Definition\TypeInterface;
16
use Digia\GraphQL\Type\SchemaInterface;
17
use function Digia\GraphQL\Util\suggestionList;
18
19
class FieldOnCorrectTypeRule extends AbstractRule
20
{
21
    /**
22
     * @inheritdoc
23
     */
24
    public function enterNode(NodeInterface $node): ?NodeInterface
25
    {
26
        if ($node instanceof FieldNode) {
27
            $type = $this->context->getParentType();
28
29
            if (null !== $type && $type instanceof OutputTypeInterface) {
30
                $fieldDefinition = $this->context->getFieldDefinition();
31
32
                if (null === $fieldDefinition) {
33
                    $schema              = $this->context->getSchema();
34
                    $fieldName           = $node->getNameValue();
35
                    $suggestedTypeNames  = getSuggestedTypeNames($schema, $type, $fieldName);
36
                    $suggestedFieldNames = count($suggestedTypeNames) !== 0
37
                        ? []
38
                        : getSuggestedFieldNames($type, $fieldName);
39
40
                    $this->context->reportError(
41
                        new ValidationException(
42
                            undefinedFieldMessage(
43
                                $fieldName,
44
                                (string)$type,
45
                                $suggestedTypeNames,
46
                                $suggestedFieldNames
47
                            ),
48
                            [$node]
49
                        )
50
                    );
51
                }
52
            }
53
        }
54
55
        return $node;
56
    }
57
}
58
59
/**
60
 * Go through all of the implementations of type, as well as the interfaces
61
 * that they implement. If any of those types include the provided field,
62
 * suggest them, sorted by how often the type is referenced,  starting
63
 * with Interfaces.
64
 *
65
 * @param SchemaInterface $schema
66
 * @param TypeInterface   $type
67
 * @param string          $fieldName
68
 * @return array
69
 * @throws \Exception
70
 */
71
function getSuggestedTypeNames(SchemaInterface $schema, TypeInterface $type, string $fieldName): array
72
{
73
    if (!$type instanceof AbstractTypeInterface) {
74
        // Otherwise, must be an Object type, which does not have possible fields.
75
        return [];
76
    }
77
78
    $suggestedObjectTypes = [];
79
    $interfaceUsageCount  = [];
80
81
    /** @var FieldsTrait|NameTrait|InterfacesTrait $possibleType */
82
    foreach ($schema->getPossibleTypes($type) as $possibleType) {
83
        $typeFields = $possibleType->getFields();
84
        if (!isset($typeFields[$fieldName])) {
85
            break;
86
        }
87
88
        $suggestedObjectTypes[] = $possibleType->getName();
89
90
        /** @var InterfaceType $possibleInterface */
91
        foreach ($possibleType->getInterfaces() as $possibleInterface) {
92
            $interfaceFields = $possibleInterface->getFields();
93
            if (!isset($interfaceFields[$fieldName])) {
94
                break;
95
            }
96
97
            $interfaceName                       = $possibleInterface->getName();
98
            $interfaceUsageCount[$interfaceName] = ($interfaceUsageCount[$interfaceName] ?? 0) + 1;
99
        }
100
    }
101
102
    $suggestedInterfaceTypes = array_keys($interfaceUsageCount);
103
104
    uasort($suggestedInterfaceTypes, function ($a, $b) use ($interfaceUsageCount) {
105
        return $interfaceUsageCount[$b] - $interfaceUsageCount[$a];
106
    });
107
108
    return array_merge($suggestedInterfaceTypes, $suggestedObjectTypes);
109
}
110
111
/**
112
 * For the field name provided, determine if there are any similar field names
113
 * that may be the result of a typo.
114
 *
115
 * @param OutputTypeInterface $type
116
 * @param string              $fieldName
117
 * @return array
118
 * @throws \Exception
119
 */
120
function getSuggestedFieldNames(OutputTypeInterface $type, string $fieldName): array
121
{
122
    if (!($type instanceof ObjectType || $type instanceof InterfaceType)) {
123
        // Otherwise, must be a Union type, which does not define fields.
124
        return [];
125
    }
126
127
    /** @var FieldsTrait $type */
128
    $possibleFieldNames = array_keys($type->getFields());
129
    return suggestionList($fieldName, $possibleFieldNames);
130
}
131