Failed Conditions
Push — master ( 48b44f...392b56 )
by Vladimir
04:42
created

DocumentValidator::validate()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

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