Failed Conditions
Push — master ( 177394...bf4e7d )
by Vladimir
09:19
created

VariablesInAllowedPosition::allowedVariableUsage()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 14
ccs 9
cts 9
cp 1
rs 9.2222
c 0
b 0
f 0
cc 6
nc 5
nop 5
crap 6
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\NodeKind;
9
use GraphQL\Language\AST\NullValueNode;
10
use GraphQL\Language\AST\OperationDefinitionNode;
11
use GraphQL\Language\AST\ValueNode;
12
use GraphQL\Language\AST\VariableDefinitionNode;
13
use GraphQL\Type\Definition\ListOfType;
14
use GraphQL\Type\Definition\NonNull;
15
use GraphQL\Type\Definition\Type;
16
use GraphQL\Type\Schema;
17
use GraphQL\Utils\TypeComparators;
18
use GraphQL\Utils\TypeInfo;
19
use GraphQL\Utils\Utils;
20
use GraphQL\Validator\ValidationContext;
21
use function sprintf;
22
23
class VariablesInAllowedPosition extends ValidationRule
24
{
25
    /**
26
     * A map from variable names to their definition nodes.
27
     *
28
     * @var VariableDefinitionNode[]
29
     */
30
    public $varDefMap;
31
32 138
    public function getVisitor(ValidationContext $context)
33
    {
34
        return [
35
            NodeKind::OPERATION_DEFINITION => [
36
                'enter' => function () {
37 137
                    $this->varDefMap = [];
38 138
                },
39
                'leave' => function (OperationDefinitionNode $operation) use ($context) {
40 137
                    $usages = $context->getRecursiveVariableUsages($operation);
41
42 137
                    foreach ($usages as $usage) {
43 35
                        $node         = $usage['node'];
44 35
                        $type         = $usage['type'];
45 35
                        $defaultValue = $usage['defaultValue'];
46 35
                        $varName      = $node->name->value;
47 35
                        $varDef       = $this->varDefMap[$varName] ?? null;
48
49 35
                        if ($varDef === null || $type === null) {
50 3
                            continue;
51
                        }
52
53
                        // A var type is allowed if it is the same or more strict (e.g. is
54
                        // a subtype of) than the expected type. It can be more strict if
55
                        // the variable type is non-null when the expected type is nullable.
56
                        // If both are list types, the variable item type can be more strict
57
                        // than the expected item type (contravariant).
58 32
                        $schema  = $context->getSchema();
59 32
                        $varType = TypeInfo::typeFromAST($schema, $varDef->type);
60
61 32
                        if (! $varType || $this->allowedVariableUsage($schema, $varType, $varDef->defaultValue, $type, $defaultValue)) {
62 21
                            continue;
63
                        }
64
65 11
                        $context->reportError(new Error(
66 11
                            self::badVarPosMessage($varName, $varType, $type),
67 11
                            [$varDef, $node]
68
                        ));
69
                    }
70 138
                },
71
            ],
72
            NodeKind::VARIABLE_DEFINITION  => function (VariableDefinitionNode $varDefNode) {
73 35
                $this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
74 138
            },
75
        ];
76
    }
77
78
    /**
79
     * A var type is allowed if it is the same or more strict than the expected
80
     * type. It can be more strict if the variable type is non-null when the
81
     * expected type is nullable. If both are list types, the variable item type can
82
     * be more strict than the expected item type.
83
     */
84 11
    public static function badVarPosMessage($varName, $varType, $expectedType)
85
    {
86 11
        return sprintf(
87 11
            'Variable "$%s" of type "%s" used in position expecting type "%s".',
88 11
            $varName,
89 11
            $varType,
90 11
            $expectedType
91
        );
92
    }
93
94
    /**
95
     * Returns true if the variable is allowed in the location it was found,
96
     * which includes considering if default values exist for either the variable
97
     * or the location at which it is located.
98
     *
99
     * @param ValueNode|null $varDefaultValue
100
     * @param mixed          $locationDefaultValue
101
     */
102 32
    private function allowedVariableUsage(Schema $schema, Type $varType, $varDefaultValue, Type $locationType, $locationDefaultValue) : bool
103
    {
104 32
        if ($locationType instanceof NonNull && ! $varType instanceof NonNull) {
105 8
            $hasNonNullVariableDefaultValue = $varDefaultValue && ! $varDefaultValue instanceof NullValueNode;
106 8
            $hasLocationDefaultValue        = ! Utils::isInvalid($locationDefaultValue);
107 8
            if (! $hasNonNullVariableDefaultValue && ! $hasLocationDefaultValue) {
108 6
                return false;
109
            }
110 2
            $nullableLocationType = $locationType->getWrappedType();
111
112 2
            return TypeComparators::isTypeSubTypeOf($schema, $varType, $nullableLocationType);
113
        }
114
115 24
        return TypeComparators::isTypeSubTypeOf($schema, $varType, $locationType);
116
    }
117
}
118