Completed
Push — master ( 974258...005b1a )
by Vladimir
19:57 queued 16:19
created

VariablesInAllowedPosition::getVisitor()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6.0029

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 45
ccs 22
cts 23
cp 0.9565
rs 8.8977
c 0
b 0
f 0
cc 6
nc 1
nop 1
crap 6.0029
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 104
    public function getVisitor(ValidationContext $context)
24
    {
25
        return [
26
            NodeKind::OPERATION_DEFINITION => [
27
                'enter' => function () {
28 103
                    $this->varDefMap = [];
29 104
                },
30
                'leave' => function (OperationDefinitionNode $operation) use ($context) {
31 103
                    $usages = $context->getRecursiveVariableUsages($operation);
32
33 103
                    foreach ($usages as $usage) {
34 11
                        $node    = $usage['node'];
35 11
                        $type    = $usage['type'];
36 11
                        $varName = $node->name->value;
37 11
                        $varDef  = $this->varDefMap[$varName] ?? null;
38
39 11
                        if ($varDef === null || $type === null) {
40
                            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 11
                        $schema  = $context->getSchema();
49 11
                        $varType = TypeInfo::typeFromAST($schema, $varDef->type);
50
51 11
                        if (! $varType || TypeComparators::isTypeSubTypeOf(
52 11
                            $schema,
53 11
                            $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 11
                            $type
55
                        )) {
56 9
                            continue;
57
                        }
58
59 2
                        $context->reportError(new Error(
60 2
                            self::badVarPosMessage($varName, $varType, $type),
61 2
                            [$varDef, $node]
62
                        ));
63
                    }
64 104
                },
65
            ],
66
            NodeKind::VARIABLE_DEFINITION  => function (VariableDefinitionNode $varDefNode) {
67 11
                $this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
68 104
            },
69
        ];
70
    }
71
72 11
    private function effectiveType($varType, $varDef)
73
    {
74 11
        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 2
    public static function badVarPosMessage($varName, $varType, $expectedType)
84
    {
85 2
        return sprintf(
86 2
            'Variable "$%s" of type "%s" used in position expecting type "%s".',
87 2
            $varName,
88 2
            $varType,
89 2
            $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