KnownDirectives::unknownDirectiveMessage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Validator\Rules;
6
7
use Exception;
8
use GraphQL\Error\Error;
9
use GraphQL\Language\AST\DirectiveDefinitionNode;
10
use GraphQL\Language\AST\DirectiveNode;
11
use GraphQL\Language\AST\EnumTypeDefinitionNode;
12
use GraphQL\Language\AST\EnumTypeExtensionNode;
13
use GraphQL\Language\AST\EnumValueDefinitionNode;
14
use GraphQL\Language\AST\FieldDefinitionNode;
15
use GraphQL\Language\AST\FieldNode;
16
use GraphQL\Language\AST\FragmentDefinitionNode;
17
use GraphQL\Language\AST\FragmentSpreadNode;
18
use GraphQL\Language\AST\InlineFragmentNode;
19
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
20
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
21
use GraphQL\Language\AST\InputValueDefinitionNode;
22
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
23
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
24
use GraphQL\Language\AST\Node;
25
use GraphQL\Language\AST\NodeKind;
26
use GraphQL\Language\AST\NodeList;
27
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
28
use GraphQL\Language\AST\ObjectTypeExtensionNode;
29
use GraphQL\Language\AST\OperationDefinitionNode;
30
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
31
use GraphQL\Language\AST\ScalarTypeExtensionNode;
32
use GraphQL\Language\AST\SchemaDefinitionNode;
33
use GraphQL\Language\AST\SchemaTypeExtensionNode;
34
use GraphQL\Language\AST\UnionTypeDefinitionNode;
35
use GraphQL\Language\AST\UnionTypeExtensionNode;
36
use GraphQL\Language\AST\VariableDefinitionNode;
37
use GraphQL\Language\DirectiveLocation;
38
use GraphQL\Type\Definition\Directive;
39
use GraphQL\Validator\ASTValidationContext;
40
use GraphQL\Validator\SDLValidationContext;
41
use GraphQL\Validator\ValidationContext;
42
use function array_map;
43
use function count;
44
use function get_class;
45
use function in_array;
46
use function sprintf;
47
48
class KnownDirectives extends ValidationRule
49
{
50 127
    public function getVisitor(ValidationContext $context)
51
    {
52 127
        return $this->getASTVisitor($context);
53
    }
54
55 207
    public function getSDLVisitor(SDLValidationContext $context)
56
    {
57 207
        return $this->getASTVisitor($context);
58
    }
59
60 319
    public function getASTVisitor(ASTValidationContext $context)
61
    {
62 319
        $locationsMap      = [];
63 319
        $schema            = $context->getSchema();
64 319
        $definedDirectives = $schema
0 ignored issues
show
introduced by
$schema is of type GraphQL\Type\Schema, thus it always evaluated to true.
Loading history...
65 194
            ? $schema->getDirectives()
66 319
            : Directive::getInternalDirectives();
67
68 319
        foreach ($definedDirectives as $directive) {
69 319
            $locationsMap[$directive->name] = $directive->locations;
70
        }
71
72 319
        $astDefinition = $context->getDocument()->definitions;
73
74 319
        foreach ($astDefinition as $def) {
75 319
            if (! ($def instanceof DirectiveDefinitionNode)) {
76 313
                continue;
77
            }
78
79 37
            $locationsMap[$def->name->value] = array_map(
80
                static function ($name) {
81 37
                    return $name->value;
82 37
                },
83 37
                $def->locations
84
            );
85
        }
86
87
        return [
88
            NodeKind::DIRECTIVE => function (
89
                DirectiveNode $node,
90
                $key,
91
                $parent,
92
                $path,
93
                $ancestors
94
            ) use (
95 32
                $context,
96 32
                $locationsMap
97
            ) {
98 32
                $name      = $node->name->value;
99 32
                $locations = $locationsMap[$name] ?? null;
100
101 32
                if (! $locations) {
102 5
                    $context->reportError(new Error(
103 5
                        self::unknownDirectiveMessage($name),
104 5
                        [$node]
105
                    ));
106
107 5
                    return;
108
                }
109
110 27
                $candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
111
112 27
                if (! $candidateLocation || in_array($candidateLocation, $locations, true)) {
113 25
                    return;
114
                }
115 3
                $context->reportError(
116 3
                    new Error(
117 3
                        self::misplacedDirectiveMessage($name, $candidateLocation),
118 3
                        [$node]
119
                    )
120
                );
121 319
            },
122
        ];
123
    }
124
125 5
    public static function unknownDirectiveMessage($directiveName)
126
    {
127 5
        return sprintf('Unknown directive "%s".', $directiveName);
128
    }
129
130
    /**
131
     * @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
132
     *
133
     * @return string
134
     */
135 27
    private function getDirectiveLocationForASTPath(array $ancestors)
136
    {
137 27
        $appliedTo = $ancestors[count($ancestors) - 1];
138
        switch (true) {
139 27
            case $appliedTo instanceof OperationDefinitionNode:
140 2
                switch ($appliedTo->operation) {
141 2
                    case 'query':
142 2
                        return DirectiveLocation::QUERY;
143 2
                    case 'mutation':
144 2
                        return DirectiveLocation::MUTATION;
145
                    case 'subscription':
146
                        return DirectiveLocation::SUBSCRIPTION;
147
                }
148
                break;
149 27
            case $appliedTo instanceof FieldNode:
150 3
                return DirectiveLocation::FIELD;
151 26
            case $appliedTo instanceof FragmentSpreadNode:
152 2
                return DirectiveLocation::FRAGMENT_SPREAD;
153 24
            case $appliedTo instanceof InlineFragmentNode:
154
                return DirectiveLocation::INLINE_FRAGMENT;
155 24
            case $appliedTo instanceof FragmentDefinitionNode:
156
                return DirectiveLocation::FRAGMENT_DEFINITION;
157 24
            case $appliedTo instanceof VariableDefinitionNode:
158 2
                return DirectiveLocation::VARIABLE_DEFINITION;
159 22
            case $appliedTo instanceof SchemaDefinitionNode:
160 20
            case $appliedTo instanceof SchemaTypeExtensionNode:
161 6
                return DirectiveLocation::SCHEMA;
162 19
            case $appliedTo instanceof ScalarTypeDefinitionNode:
163 19
            case $appliedTo instanceof ScalarTypeExtensionNode:
164 6
                return DirectiveLocation::SCALAR;
165 17
            case $appliedTo instanceof ObjectTypeDefinitionNode:
166 17
            case $appliedTo instanceof ObjectTypeExtensionNode:
167 8
                return DirectiveLocation::OBJECT;
168 14
            case $appliedTo instanceof FieldDefinitionNode:
169 8
                return DirectiveLocation::FIELD_DEFINITION;
170 11
            case $appliedTo instanceof InterfaceTypeDefinitionNode:
171 11
            case $appliedTo instanceof InterfaceTypeExtensionNode:
172 5
                return DirectiveLocation::IFACE;
173 11
            case $appliedTo instanceof UnionTypeDefinitionNode:
174 11
            case $appliedTo instanceof UnionTypeExtensionNode:
175 6
                return DirectiveLocation::UNION;
176 10
            case $appliedTo instanceof EnumTypeDefinitionNode:
177 10
            case $appliedTo instanceof EnumTypeExtensionNode:
178 6
                return DirectiveLocation::ENUM;
179 9
            case $appliedTo instanceof EnumValueDefinitionNode:
180 6
                return DirectiveLocation::ENUM_VALUE;
181 6
            case $appliedTo instanceof InputObjectTypeDefinitionNode:
182 6
            case $appliedTo instanceof InputObjectTypeExtensionNode:
183 6
                return DirectiveLocation::INPUT_OBJECT;
184 3
            case $appliedTo instanceof InputValueDefinitionNode:
185 3
                $parentNode = $ancestors[count($ancestors) - 3];
186
187 3
                return $parentNode instanceof InputObjectTypeDefinitionNode
188 3
                    ? DirectiveLocation::INPUT_FIELD_DEFINITION
189 3
                    : DirectiveLocation::ARGUMENT_DEFINITION;
190
        }
191
192
        throw new Exception('Unknown directive location: ' . get_class($appliedTo));
193
    }
194
195 3
    public static function misplacedDirectiveMessage($directiveName, $location)
196
    {
197 3
        return sprintf('Directive "%s" may not be used on "%s".', $directiveName, $location);
198
    }
199
}
200