Failed Conditions
Push — master ( 387f41...747cb4 )
by Vladimir
10:09 queued 12s
created

KnownDirectives   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Test Coverage

Coverage 92.94%

Importance

Changes 0
Metric Value
wmc 36
eloc 88
dl 0
loc 134
ccs 79
cts 85
cp 0.9294
rs 9.52
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
D getDirectiveLocationForASTPath() 0 53 27
A unknownDirectiveMessage() 0 3 1
B getVisitor() 0 57 7
A misplacedDirectiveMessage() 0 3 1
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\DirectiveDefinitionNode;
9
use GraphQL\Language\AST\DirectiveNode;
10
use GraphQL\Language\AST\EnumTypeDefinitionNode;
11
use GraphQL\Language\AST\EnumTypeExtensionNode;
12
use GraphQL\Language\AST\EnumValueDefinitionNode;
13
use GraphQL\Language\AST\FieldDefinitionNode;
14
use GraphQL\Language\AST\FieldNode;
15
use GraphQL\Language\AST\FragmentDefinitionNode;
16
use GraphQL\Language\AST\FragmentSpreadNode;
17
use GraphQL\Language\AST\InlineFragmentNode;
18
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
19
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
20
use GraphQL\Language\AST\InputValueDefinitionNode;
21
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
22
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
23
use GraphQL\Language\AST\Node;
24
use GraphQL\Language\AST\NodeKind;
25
use GraphQL\Language\AST\NodeList;
26
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
27
use GraphQL\Language\AST\ObjectTypeExtensionNode;
28
use GraphQL\Language\AST\OperationDefinitionNode;
29
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
30
use GraphQL\Language\AST\ScalarTypeExtensionNode;
31
use GraphQL\Language\AST\SchemaDefinitionNode;
32
use GraphQL\Language\AST\SchemaTypeExtensionNode;
33
use GraphQL\Language\AST\UnionTypeDefinitionNode;
34
use GraphQL\Language\AST\UnionTypeExtensionNode;
35
use GraphQL\Language\DirectiveLocation;
36
use GraphQL\Validator\ValidationContext;
37
use function array_map;
38
use function count;
39
use function in_array;
40
use function sprintf;
41
42
class KnownDirectives extends ValidationRule
43
{
44 169
    public function getVisitor(ValidationContext $context)
45
    {
46 169
        $locationsMap      = [];
47 169
        $schema            = $context->getSchema();
48 169
        $definedDirectives = $schema->getDirectives();
49
50 169
        foreach ($definedDirectives as $directive) {
51 169
            $locationsMap[$directive->name] = $directive->locations;
52
        }
53
54 169
        $astDefinition = $context->getDocument()->definitions;
55
56 169
        foreach ($astDefinition as $def) {
57 169
            if (! ($def instanceof DirectiveDefinitionNode)) {
58 163
                continue;
59
            }
60
61 7
            $locationsMap[$def->name->value] = array_map(
62
                static function ($name) {
63 7
                    return $name->value;
64 7
                },
65 7
                $def->locations
66
            );
67
        }
68
69
        return [
70
            NodeKind::DIRECTIVE => function (
71
                DirectiveNode $node,
72
                $key,
73
                $parent,
74
                $path,
75
                $ancestors
76
            ) use (
77 17
                $context,
78 17
                $locationsMap
79
            ) {
80 17
                $name      = $node->name->value;
81 17
                $locations = $locationsMap[$name] ?? null;
82
83 17
                if (! $locations) {
84 3
                    $context->reportError(new Error(
85 3
                        self::unknownDirectiveMessage($name),
86 3
                        [$node]
87
                    ));
88
89 3
                    return;
90
                }
91
92 14
                $candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
93
94 14
                if (! $candidateLocation || in_array($candidateLocation, $locations, true)) {
95 12
                    return;
96
                }
97 2
                $context->reportError(
98 2
                    new Error(
99 2
                        self::misplacedDirectiveMessage($name, $candidateLocation),
100 2
                        [$node]
101
                    )
102
                );
103 169
            },
104
        ];
105
    }
106
107 3
    public static function unknownDirectiveMessage($directiveName)
108
    {
109 3
        return sprintf('Unknown directive "%s".', $directiveName);
110
    }
111
112
    /**
113
     * @param Node[]|NodeList[] $ancestors The type is actually (Node|NodeList)[] but this PSR-5 syntax is so far not supported by most of the tools
114
     *
115
     * @return string
116
     */
117 14
    private function getDirectiveLocationForASTPath(array $ancestors)
118
    {
119 14
        $appliedTo = $ancestors[count($ancestors) - 1];
120
        switch (true) {
121 14
            case $appliedTo instanceof OperationDefinitionNode:
122 2
                switch ($appliedTo->operation) {
123 2
                    case 'query':
124 2
                        return DirectiveLocation::QUERY;
125 2
                    case 'mutation':
126 2
                        return DirectiveLocation::MUTATION;
127
                    case 'subscription':
128
                        return DirectiveLocation::SUBSCRIPTION;
129
                }
130
                break;
131 14
            case $appliedTo instanceof FieldNode:
132 3
                return DirectiveLocation::FIELD;
133 13
            case $appliedTo instanceof FragmentSpreadNode:
134 2
                return DirectiveLocation::FRAGMENT_SPREAD;
135 11
            case $appliedTo instanceof InlineFragmentNode:
136
                return DirectiveLocation::INLINE_FRAGMENT;
137 11
            case $appliedTo instanceof FragmentDefinitionNode:
138
                return DirectiveLocation::FRAGMENT_DEFINITION;
139 11
            case $appliedTo instanceof SchemaDefinitionNode:
140 10
            case $appliedTo instanceof SchemaTypeExtensionNode:
141 4
                return DirectiveLocation::SCHEMA;
142 9
            case $appliedTo instanceof ScalarTypeDefinitionNode:
143 9
            case $appliedTo instanceof ScalarTypeExtensionNode:
144 5
                return DirectiveLocation::SCALAR;
145 7
            case $appliedTo instanceof ObjectTypeDefinitionNode:
146 7
            case $appliedTo instanceof ObjectTypeExtensionNode:
147 4
                return DirectiveLocation::OBJECT;
148 7
            case $appliedTo instanceof FieldDefinitionNode:
149 4
                return DirectiveLocation::FIELD_DEFINITION;
150 6
            case $appliedTo instanceof InterfaceTypeDefinitionNode:
151 6
            case $appliedTo instanceof InterfaceTypeExtensionNode:
152 4
                return DirectiveLocation::IFACE;
153 6
            case $appliedTo instanceof UnionTypeDefinitionNode:
154 6
            case $appliedTo instanceof UnionTypeExtensionNode:
155 4
                return DirectiveLocation::UNION;
156 6
            case $appliedTo instanceof EnumTypeDefinitionNode:
157 6
            case $appliedTo instanceof EnumTypeExtensionNode:
158 4
                return DirectiveLocation::ENUM;
159 6
            case $appliedTo instanceof EnumValueDefinitionNode:
160 4
                return DirectiveLocation::ENUM_VALUE;
161 4
            case $appliedTo instanceof InputObjectTypeDefinitionNode:
162 4
            case $appliedTo instanceof InputObjectTypeExtensionNode:
163 4
                return DirectiveLocation::INPUT_OBJECT;
164 2
            case $appliedTo instanceof InputValueDefinitionNode:
165 2
                $parentNode = $ancestors[count($ancestors) - 3];
166
167 2
                return $parentNode instanceof InputObjectTypeDefinitionNode
168 2
                    ? DirectiveLocation::INPUT_FIELD_DEFINITION
169 2
                    : DirectiveLocation::ARGUMENT_DEFINITION;
170
        }
171
    }
172
173 2
    public static function misplacedDirectiveMessage($directiveName, $location)
174
    {
175 2
        return sprintf('Directive "%s" may not be used on "%s".', $directiveName, $location);
176
    }
177
}
178