Failed Conditions
Push — master ( 7ff3e9...e7de06 )
by Vladimir
04:33 queued 01:54
created

KnownDirectives   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Test Coverage

Coverage 92.68%

Importance

Changes 0
Metric Value
wmc 36
eloc 79
dl 0
loc 120
ccs 76
cts 82
cp 0.9268
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
A misplacedDirectiveMessage() 0 3 1
B getVisitor() 0 43 7
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
            $locationsMap[$def->name->value] = array_map(static function ($name) {
41 7
                return $name->value;
42 7
            }, $def->locations);
43
        }
44
        return [
45
            NodeKind::DIRECTIVE => function (DirectiveNode $node, $key, $parent, $path, $ancestors) use ($context, $locationsMap) {
46 17
                $name      = $node->name->value;
47 17
                $locations = $locationsMap[$name] ?? null;
48
49 17
                if (! $locations) {
50 3
                    $context->reportError(new Error(
51 3
                        self::unknownDirectiveMessage($name),
52 3
                        [$node]
53
                    ));
54 3
                    return;
55
                }
56
57 14
                $candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
58
59 14
                if (! $candidateLocation || in_array($candidateLocation, $locations)) {
60 12
                    return;
61
                }
62 2
                $context->reportError(
63 2
                    new Error(
64 2
                        self::misplacedDirectiveMessage($name, $candidateLocation),
65 2
                        [$node]
66
                    )
67
                );
68 165
            },
69
        ];
70
    }
71
72 3
    public static function unknownDirectiveMessage($directiveName)
73
    {
74 3
        return sprintf('Unknown directive "%s".', $directiveName);
75
    }
76
77
    /**
78
     * @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
79
     *
80
     * @return string
81
     */
82 14
    private function getDirectiveLocationForASTPath(array $ancestors)
83
    {
84 14
        $appliedTo = $ancestors[count($ancestors) - 1];
85 14
        switch ($appliedTo->kind) {
86 14
            case NodeKind::OPERATION_DEFINITION:
87 2
                switch ($appliedTo->operation) {
88 2
                    case 'query':
89 2
                        return DirectiveLocation::QUERY;
90 2
                    case 'mutation':
91 2
                        return DirectiveLocation::MUTATION;
92
                    case 'subscription':
93
                        return DirectiveLocation::SUBSCRIPTION;
94
                }
95
                break;
96 14
            case NodeKind::FIELD:
97 3
                return DirectiveLocation::FIELD;
98 13
            case NodeKind::FRAGMENT_SPREAD:
99 2
                return DirectiveLocation::FRAGMENT_SPREAD;
100 11
            case NodeKind::INLINE_FRAGMENT:
101
                return DirectiveLocation::INLINE_FRAGMENT;
102 11
            case NodeKind::FRAGMENT_DEFINITION:
103
                return DirectiveLocation::FRAGMENT_DEFINITION;
104 11
            case NodeKind::SCHEMA_DEFINITION:
105 10
            case NodeKind::SCHEMA_EXTENSION:
106 4
                return DirectiveLocation::SCHEMA;
107 9
            case NodeKind::SCALAR_TYPE_DEFINITION:
108 9
            case NodeKind::SCALAR_TYPE_EXTENSION:
109 5
                return DirectiveLocation::SCALAR;
110 7
            case NodeKind::OBJECT_TYPE_DEFINITION:
111 7
            case NodeKind::OBJECT_TYPE_EXTENSION:
112 4
                return DirectiveLocation::OBJECT;
113 7
            case NodeKind::FIELD_DEFINITION:
114 4
                return DirectiveLocation::FIELD_DEFINITION;
115 6
            case NodeKind::INTERFACE_TYPE_DEFINITION:
116 6
            case NodeKind::INTERFACE_TYPE_EXTENSION:
117 4
                return DirectiveLocation::IFACE;
118 6
            case NodeKind::UNION_TYPE_DEFINITION:
119 6
            case NodeKind::UNION_TYPE_EXTENSION:
120 4
                return DirectiveLocation::UNION;
121 6
            case NodeKind::ENUM_TYPE_DEFINITION:
122 6
            case NodeKind::ENUM_TYPE_EXTENSION:
123 4
                return DirectiveLocation::ENUM;
124 6
            case NodeKind::ENUM_VALUE_DEFINITION:
125 4
                return DirectiveLocation::ENUM_VALUE;
126 4
            case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
127 4
            case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
128 4
                return DirectiveLocation::INPUT_OBJECT;
129 2
            case NodeKind::INPUT_VALUE_DEFINITION:
130 2
                $parentNode = $ancestors[count($ancestors) - 3];
131
132 2
                return $parentNode instanceof InputObjectTypeDefinitionNode
133 2
                    ? DirectiveLocation::INPUT_FIELD_DEFINITION
134 2
                    : DirectiveLocation::ARGUMENT_DEFINITION;
135
        }
136
    }
137
138 2
    public static function misplacedDirectiveMessage($directiveName, $location)
139
    {
140 2
        return sprintf('Directive "%s" may not be used on "%s".', $directiveName, $location);
141
    }
142
}
143