Failed Conditions
Push — master ( 7ff3e9...e7de06 )
by Vladimir
04:33 queued 01:54
created

DocumentValidator::append()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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