Failed Conditions
Push — master ( e31947...cc39b3 )
by Šimon
11s
created

DocumentValidator::visitUsingRules()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 4
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Validator;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\AST\DocumentNode;
9
use GraphQL\Language\Visitor;
10
use GraphQL\Type\Definition\Type;
11
use GraphQL\Type\Schema;
12
use GraphQL\Utils\TypeInfo;
13
use GraphQL\Validator\Rules\DisableIntrospection;
14
use GraphQL\Validator\Rules\ExecutableDefinitions;
15
use GraphQL\Validator\Rules\FieldsOnCorrectType;
16
use GraphQL\Validator\Rules\FragmentsOnCompositeTypes;
17
use GraphQL\Validator\Rules\KnownArgumentNames;
18
use GraphQL\Validator\Rules\KnownDirectives;
19
use GraphQL\Validator\Rules\KnownFragmentNames;
20
use GraphQL\Validator\Rules\KnownTypeNames;
21
use GraphQL\Validator\Rules\LoneAnonymousOperation;
22
use GraphQL\Validator\Rules\NoFragmentCycles;
23
use GraphQL\Validator\Rules\NoUndefinedVariables;
24
use GraphQL\Validator\Rules\NoUnusedFragments;
25
use GraphQL\Validator\Rules\NoUnusedVariables;
26
use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged;
27
use GraphQL\Validator\Rules\PossibleFragmentSpreads;
28
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
29
use GraphQL\Validator\Rules\QueryComplexity;
30
use GraphQL\Validator\Rules\QueryDepth;
31
use GraphQL\Validator\Rules\QuerySecurityRule;
32
use GraphQL\Validator\Rules\ScalarLeafs;
33
use GraphQL\Validator\Rules\UniqueArgumentNames;
34
use GraphQL\Validator\Rules\UniqueDirectivesPerLocation;
35
use GraphQL\Validator\Rules\UniqueFragmentNames;
36
use GraphQL\Validator\Rules\UniqueInputFieldNames;
37
use GraphQL\Validator\Rules\UniqueOperationNames;
38
use GraphQL\Validator\Rules\UniqueVariableNames;
39
use GraphQL\Validator\Rules\ValidationRule;
40
use GraphQL\Validator\Rules\ValuesOfCorrectType;
41
use GraphQL\Validator\Rules\VariablesAreInputTypes;
42
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
43
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
44
use function array_filter;
45
use function array_merge;
46
use function count;
47
use function is_array;
48
use function sprintf;
49
50
/**
51
 * Implements the "Validation" section of the spec.
52
 *
53
 * Validation runs synchronously, returning an array of encountered errors, or
54
 * an empty array if no errors were encountered and the document is valid.
55
 *
56
 * A list of specific validation rules may be provided. If not provided, the
57
 * default list of rules defined by the GraphQL specification will be used.
58
 *
59
 * Each validation rule is an instance of GraphQL\Validator\Rules\ValidationRule
60
 * which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
61
 *
62
 * Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
63
 * or array of such instances when invalid.
64
 *
65
 * Optionally a custom TypeInfo instance may be provided. If not provided, one
66
 * will be created from the provided schema.
67
 */
68
class DocumentValidator
69
{
70
    /** @var ValidationRule[] */
71
    private static $rules = [];
72
73
    /** @var ValidationRule[]|null */
74
    private static $defaultRules;
75
76
    /** @var QuerySecurityRule[]|null */
77
    private static $securityRules;
78
79
    /** @var bool */
80
    private static $initRules = false;
81
82
    /**
83
     * Primary method for query validation. See class description for details.
84
     *
85
     * @api
86
     * @param ValidationRule[]|null $rules
87
     * @return Error[]
88
     */
89 514
    public static function validate(
90
        Schema $schema,
91
        DocumentNode $ast,
92
        ?array $rules = null,
93
        ?TypeInfo $typeInfo = null
94
    ) {
95 514
        if ($rules === null) {
96 98
            $rules = static::allRules();
97
        }
98
99 514
        if (is_array($rules) === true && count($rules) === 0) {
100
            // Skip validation if there are no rules
101 4
            return [];
102
        }
103
104 512
        $typeInfo = $typeInfo ?: new TypeInfo($schema);
105
106 512
        return static::visitUsingRules($schema, $typeInfo, $ast, $rules);
107
    }
108
109
    /**
110
     * Returns all global validation rules.
111
     *
112
     * @api
113
     * @return ValidationRule[]
114
     */
115 104
    public static function allRules()
116
    {
117 104
        if (! self::$initRules) {
118 1
            static::$rules     = array_merge(static::defaultRules(), self::securityRules(), self::$rules);
0 ignored issues
show
Bug introduced by
Since $rules is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $rules to at least protected.
Loading history...
119 1
            static::$initRules = true;
0 ignored issues
show
Bug introduced by
Since $initRules is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $initRules to at least protected.
Loading history...
120
        }
121
122 104
        return self::$rules;
123
    }
124
125 1
    public static function defaultRules()
126
    {
127 1
        if (self::$defaultRules === null) {
128
            self::$defaultRules = [
129 1
                ExecutableDefinitions::class        => new ExecutableDefinitions(),
130 1
                UniqueOperationNames::class         => new UniqueOperationNames(),
131 1
                LoneAnonymousOperation::class       => new LoneAnonymousOperation(),
132 1
                KnownTypeNames::class               => new KnownTypeNames(),
133 1
                FragmentsOnCompositeTypes::class    => new FragmentsOnCompositeTypes(),
134 1
                VariablesAreInputTypes::class       => new VariablesAreInputTypes(),
135 1
                ScalarLeafs::class                  => new ScalarLeafs(),
136 1
                FieldsOnCorrectType::class          => new FieldsOnCorrectType(),
137 1
                UniqueFragmentNames::class          => new UniqueFragmentNames(),
138 1
                KnownFragmentNames::class           => new KnownFragmentNames(),
139 1
                NoUnusedFragments::class            => new NoUnusedFragments(),
140 1
                PossibleFragmentSpreads::class      => new PossibleFragmentSpreads(),
141 1
                NoFragmentCycles::class             => new NoFragmentCycles(),
142 1
                UniqueVariableNames::class          => new UniqueVariableNames(),
143 1
                NoUndefinedVariables::class         => new NoUndefinedVariables(),
144 1
                NoUnusedVariables::class            => new NoUnusedVariables(),
145 1
                KnownDirectives::class              => new KnownDirectives(),
146 1
                UniqueDirectivesPerLocation::class  => new UniqueDirectivesPerLocation(),
147 1
                KnownArgumentNames::class           => new KnownArgumentNames(),
148 1
                UniqueArgumentNames::class          => new UniqueArgumentNames(),
149 1
                ValuesOfCorrectType::class          => new ValuesOfCorrectType(),
150 1
                ProvidedNonNullArguments::class     => new ProvidedNonNullArguments(),
151 1
                VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(),
152 1
                VariablesInAllowedPosition::class   => new VariablesInAllowedPosition(),
153 1
                OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
154 1
                UniqueInputFieldNames::class        => new UniqueInputFieldNames(),
155
            ];
156
        }
157
158 1
        return self::$defaultRules;
159
    }
160
161
    /**
162
     * @return QuerySecurityRule[]
163
     */
164 1
    public static function securityRules()
165
    {
166
        // This way of defining rules is deprecated
167
        // When custom security rule is required - it should be just added via DocumentValidator::addRule();
168
        // TODO: deprecate this
169
170 1
        if (self::$securityRules === null) {
171
            self::$securityRules = [
172 1
                DisableIntrospection::class => new DisableIntrospection(DisableIntrospection::DISABLED), // DEFAULT DISABLED
173 1
                QueryDepth::class           => new QueryDepth(QueryDepth::DISABLED), // default disabled
174 1
                QueryComplexity::class      => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
175
            ];
176
        }
177
178 1
        return self::$securityRules;
179
    }
180
181
    /**
182
     * This uses a specialized visitor which runs multiple visitors in parallel,
183
     * while maintaining the visitor skip and break API.
184
     *
185
     * @param ValidationRule[] $rules
186
     * @return Error[]
187
     */
188 512
    public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules)
189
    {
190 512
        $context  = new ValidationContext($schema, $documentNode, $typeInfo);
191 512
        $visitors = [];
192 512
        foreach ($rules as $rule) {
193 512
            $visitors[] = $rule->getVisitor($context);
194
        }
195 512
        Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
196
197 512
        return $context->getErrors();
198
    }
199
200
    /**
201
     * Returns global validation rule by name. Standard rules are named by class name, so
202
     * example usage for such rules:
203
     *
204
     * $rule = DocumentValidator::getRule(GraphQL\Validator\Rules\QueryComplexity::class);
205
     *
206
     * @api
207
     * @param string $name
208
     * @return ValidationRule
209
     */
210 93
    public static function getRule($name)
211
    {
212 93
        $rules = static::allRules();
213
214 93
        if (isset($rules[$name])) {
215 93
            return $rules[$name];
216
        }
217
218
        $name = sprintf('GraphQL\\Validator\\Rules\\%s', $name);
219
220
        return $rules[$name] ?? null;
221
    }
222
223
    /**
224
     * Add rule to list of global validation rules
225
     *
226
     * @api
227
     */
228
    public static function addRule(ValidationRule $rule)
229
    {
230
        self::$rules[$rule->getName()] = $rule;
231
    }
232
233
    public static function isError($value)
234
    {
235
        return is_array($value)
236
            ? count(array_filter(
237
                $value,
238
                function ($item) {
239
                    return $item instanceof \Exception || $item instanceof \Throwable;
240
                }
241
            )) === count($value)
242
            : ($value instanceof \Exception || $value instanceof \Throwable);
243
    }
244
245
    public static function append(&$arr, $items)
246
    {
247
        if (is_array($items)) {
248
            $arr = array_merge($arr, $items);
249
        } else {
250
            $arr[] = $items;
251
        }
252
253
        return $arr;
254
    }
255
256
    /**
257
     * Utility which determines if a value literal node is valid for an input type.
258
     *
259
     * Deprecated. Rely on validation for documents containing literal values.
260
     *
261
     * @deprecated
262
     * @return Error[]
263
     */
264 2
    public static function isValidLiteralValue(Type $type, $valueNode)
265
    {
266 2
        $emptySchema = new Schema([]);
267 2
        $emptyDoc    = new DocumentNode(['definitions' => []]);
268 2
        $typeInfo    = new TypeInfo($emptySchema, $type);
269 2
        $context     = new ValidationContext($emptySchema, $emptyDoc, $typeInfo);
270 2
        $validator   = new ValuesOfCorrectType();
271 2
        $visitor     = $validator->getVisitor($context);
272 2
        Visitor::visit($valueNode, Visitor::visitWithTypeInfo($typeInfo, $visitor));
273
274 2
        return $context->getErrors();
275
    }
276
}
277