Completed
Push — master ( a01b08...b72ba3 )
by Vladimir
16s queued 14s
created

VariablesInAllowedPosition::getVisitor()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 45
ccs 23
cts 23
cp 1
rs 8.8977
c 0
b 0
f 0
cc 6
nc 1
nop 1
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\OperationDefinitionNode;
10
use GraphQL\Language\AST\VariableDefinitionNode;
11
use GraphQL\Type\Definition\ListOfType;
12
use GraphQL\Type\Definition\NonNull;
13
use GraphQL\Utils\TypeComparators;
14
use GraphQL\Utils\TypeInfo;
15
use GraphQL\Validator\ValidationContext;
16
use function sprintf;
17
18
class VariablesInAllowedPosition extends ValidationRule
19
{
20
    /** @var */
21
    public $varDefMap;
22
23 135
    public function getVisitor(ValidationContext $context)
24
    {
25
        return [
26
            NodeKind::OPERATION_DEFINITION => [
27
                'enter' => function () {
28 134
                    $this->varDefMap = [];
29 135
                },
30
                'leave' => function (OperationDefinitionNode $operation) use ($context) {
31 134
                    $usages = $context->getRecursiveVariableUsages($operation);
32
33 134
                    foreach ($usages as $usage) {
34 33
                        $node    = $usage['node'];
35 33
                        $type    = $usage['type'];
36 33
                        $varName = $node->name->value;
37 33
                        $varDef  = $this->varDefMap[$varName] ?? null;
38
39 33
                        if ($varDef === null || $type === null) {
40 2
                            continue;
41
                        }
42
43
                        // A var type is allowed if it is the same or more strict (e.g. is
44
                        // a subtype of) than the expected type. It can be more strict if
45
                        // the variable type is non-null when the expected type is nullable.
46
                        // If both are list types, the variable item type can be more strict
47
                        // than the expected item type (contravariant).
48 31
                        $schema  = $context->getSchema();
49 31
                        $varType = TypeInfo::typeFromAST($schema, $varDef->type);
50
51 31
                        if (! $varType || TypeComparators::isTypeSubTypeOf(
52 31
                            $schema,
53 31
                            $this->effectiveType($varType, $varDef),
0 ignored issues
show
Bug introduced by
$this->effectiveType($varType, $varDef) of type GraphQL\Type\Definition\...QL\Type\Definition\Type is incompatible with the type GraphQL\Type\Definition\AbstractType expected by parameter $maybeSubType of GraphQL\Utils\TypeComparators::isTypeSubTypeOf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
                            /** @scrutinizer ignore-type */ $this->effectiveType($varType, $varDef),
Loading history...
54 31
                            $type
55
                        )) {
56 21
                            continue;
57
                        }
58
59 10
                        $context->reportError(new Error(
60 10
                            self::badVarPosMessage($varName, $varType, $type),
61 10
                            [$varDef, $node]
62
                        ));
63
                    }
64 135
                },
65
            ],
66
            NodeKind::VARIABLE_DEFINITION  => function (VariableDefinitionNode $varDefNode) {
67 33
                $this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
68 135
            },
69
        ];
70
    }
71
72 31
    private function effectiveType($varType, $varDef)
73
    {
74 31
        return ! $varDef->defaultValue || $varType instanceof NonNull ? $varType : new NonNull($varType);
75
    }
76
77
    /**
78
     * A var type is allowed if it is the same or more strict than the expected
79
     * type. It can be more strict if the variable type is non-null when the
80
     * expected type is nullable. If both are list types, the variable item type can
81
     * be more strict than the expected item type.
82
     */
83 10
    public static function badVarPosMessage($varName, $varType, $expectedType)
84
    {
85 10
        return sprintf(
86 10
            'Variable "$%s" of type "%s" used in position expecting type "%s".',
87 10
            $varName,
88 10
            $varType,
89 10
            $expectedType
90
        );
91
    }
92
93
    /** If a variable definition has a default value, it's effectively non-null. */
94
    private function varTypeAllowedForType($varType, $expectedType)
95
    {
96
        if ($expectedType instanceof NonNull) {
97
            if ($varType instanceof NonNull) {
98
                return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
99
            }
100
101
            return false;
102
        }
103
        if ($varType instanceof NonNull) {
104
            return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType);
105
        }
106
        if ($varType instanceof ListOfType && $expectedType instanceof ListOfType) {
107
            return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
108
        }
109
110
        return $varType === $expectedType;
111
    }
112
}
113