Passed
Push — master ( 5fb970...6cce67 )
by Vladimir
08:12 queued 11s
created

KnownDirectives::misplacedDirectiveMessage()   A

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 2
crap 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\InputObjectTypeDefinitionNode;
11
use GraphQL\Language\AST\Node;
12
use GraphQL\Language\AST\NodeKind;
13
use GraphQL\Language\AST\NodeList;
14
use GraphQL\Language\DirectiveLocation;
15
use GraphQL\Validator\ValidationContext;
16
use function array_map;
17
use function count;
18
use function in_array;
19
use function sprintf;
20
21
class KnownDirectives extends ValidationRule
22
{
23 165
    public function getVisitor(ValidationContext $context)
24
    {
25 165
        $locationsMap      = [];
26 165
        $schema            = $context->getSchema();
27 165
        $definedDirectives = $schema->getDirectives();
28
29 165
        foreach ($definedDirectives as $directive) {
30 165
            $locationsMap[$directive->name] = $directive->locations;
31
        }
32
33 165
        $astDefinition = $context->getDocument()->definitions;
34
35 165
        foreach ($astDefinition as $def) {
36 165
            if (! ($def instanceof DirectiveDefinitionNode)) {
37 159
                continue;
38
            }
39
40 7
            $locationsMap[$def->name->value] = array_map(
41
                static function ($name) {
42 7
                    return $name->value;
43 7
                },
44 7
                $def->locations
45
            );
46
        }
47
48
        return [
49
            NodeKind::DIRECTIVE => function (
50
                DirectiveNode $node,
51
                $key,
52
                $parent,
53
                $path,
54
                $ancestors
55
            ) use (
56 17
                $context,
57 17
                $locationsMap
58
            ) {
59 17
                $name      = $node->name->value;
60 17
                $locations = $locationsMap[$name] ?? null;
61
62 17
                if (! $locations) {
63 3
                    $context->reportError(new Error(
64 3
                        self::unknownDirectiveMessage($name),
65 3
                        [$node]
66
                    ));
67
68 3
                    return;
69
                }
70
71 14
                $candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
72
73 14
                if (! $candidateLocation || in_array($candidateLocation, $locations, true)) {
74 12
                    return;
75
                }
76 2
                $context->reportError(
77 2
                    new Error(
78 2
                        self::misplacedDirectiveMessage($name, $candidateLocation),
79 2
                        [$node]
80
                    )
81
                );
82 165
            },
83
        ];
84
    }
85
86 3
    public static function unknownDirectiveMessage($directiveName)
87
    {
88 3
        return sprintf('Unknown directive "%s".', $directiveName);
89
    }
90
91
    /**
92
     * @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
93
     *
94
     * @return string
95
     */
96 14
    private function getDirectiveLocationForASTPath(array $ancestors)
97
    {
98 14
        $appliedTo = $ancestors[count($ancestors) - 1];
99 14
        switch ($appliedTo->kind) {
100 14
            case NodeKind::OPERATION_DEFINITION:
101 2
                switch ($appliedTo->operation) {
102 2
                    case 'query':
103 2
                        return DirectiveLocation::QUERY;
104 2
                    case 'mutation':
105 2
                        return DirectiveLocation::MUTATION;
106
                    case 'subscription':
107
                        return DirectiveLocation::SUBSCRIPTION;
108
                }
109
                break;
110 14
            case NodeKind::FIELD:
111 3
                return DirectiveLocation::FIELD;
112 13
            case NodeKind::FRAGMENT_SPREAD:
113 2
                return DirectiveLocation::FRAGMENT_SPREAD;
114 11
            case NodeKind::INLINE_FRAGMENT:
115
                return DirectiveLocation::INLINE_FRAGMENT;
116 11
            case NodeKind::FRAGMENT_DEFINITION:
117
                return DirectiveLocation::FRAGMENT_DEFINITION;
118 11
            case NodeKind::SCHEMA_DEFINITION:
119 10
            case NodeKind::SCHEMA_EXTENSION:
120 4
                return DirectiveLocation::SCHEMA;
121 9
            case NodeKind::SCALAR_TYPE_DEFINITION:
122 9
            case NodeKind::SCALAR_TYPE_EXTENSION:
123 5
                return DirectiveLocation::SCALAR;
124 7
            case NodeKind::OBJECT_TYPE_DEFINITION:
125 7
            case NodeKind::OBJECT_TYPE_EXTENSION:
126 4
                return DirectiveLocation::OBJECT;
127 7
            case NodeKind::FIELD_DEFINITION:
128 4
                return DirectiveLocation::FIELD_DEFINITION;
129 6
            case NodeKind::INTERFACE_TYPE_DEFINITION:
130 6
            case NodeKind::INTERFACE_TYPE_EXTENSION:
131 4
                return DirectiveLocation::IFACE;
132 6
            case NodeKind::UNION_TYPE_DEFINITION:
133 6
            case NodeKind::UNION_TYPE_EXTENSION:
134 4
                return DirectiveLocation::UNION;
135 6
            case NodeKind::ENUM_TYPE_DEFINITION:
136 6
            case NodeKind::ENUM_TYPE_EXTENSION:
137 4
                return DirectiveLocation::ENUM;
138 6
            case NodeKind::ENUM_VALUE_DEFINITION:
139 4
                return DirectiveLocation::ENUM_VALUE;
140 4
            case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
141 4
            case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
142 4
                return DirectiveLocation::INPUT_OBJECT;
143 2
            case NodeKind::INPUT_VALUE_DEFINITION:
144 2
                $parentNode = $ancestors[count($ancestors) - 3];
145
146 2
                return $parentNode instanceof InputObjectTypeDefinitionNode
147 2
                    ? DirectiveLocation::INPUT_FIELD_DEFINITION
148 2
                    : DirectiveLocation::ARGUMENT_DEFINITION;
149
        }
150
    }
151
152 2
    public static function misplacedDirectiveMessage($directiveName, $location)
153
    {
154 2
        return sprintf('Directive "%s" may not be used on "%s".', $directiveName, $location);
155
    }
156
}
157