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

DocumentValidator::getRule()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

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