GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

FormattedError::getColumnOffset()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 1
nc 2
nop 2
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Error;
6
7
use Countable;
8
use ErrorException;
9
use GraphQL\Language\AST\Node;
10
use GraphQL\Language\Source;
11
use GraphQL\Language\SourceLocation;
12
use GraphQL\Type\Definition\Type;
13
use GraphQL\Type\Definition\WrappingType;
14
use GraphQL\Utils\Utils;
15
use Throwable;
16
use function addcslashes;
17
use function array_filter;
18
use function array_intersect_key;
19
use function array_map;
20
use function array_merge;
21
use function array_shift;
22
use function count;
23
use function get_class;
24
use function gettype;
25
use function implode;
26
use function is_array;
27
use function is_bool;
28
use function is_object;
29
use function is_scalar;
30
use function is_string;
31
use function mb_strlen;
32
use function preg_split;
33
use function sprintf;
34
use function str_repeat;
35
use function strlen;
36
37
/**
38
 * This class is used for [default error formatting](error-handling.md).
39
 * It converts PHP exceptions to [spec-compliant errors](https://facebook.github.io/graphql/#sec-Errors)
40
 * and provides tools for error debugging.
41
 */
42
class FormattedError
43
{
44
    /** @var string */
45
    private static $internalErrorMessage = 'Internal server error';
46
47
    /**
48
     * Set default error message for internal errors formatted using createFormattedError().
49
     * This value can be overridden by passing 3rd argument to `createFormattedError()`.
50
     *
51
     * @param string $msg
52
     *
53
     * @api
54
     */
55
    public static function setInternalErrorMessage($msg)
56
    {
57
        self::$internalErrorMessage = $msg;
58
    }
59
60
    /**
61
     * Prints a GraphQLError to a string, representing useful location information
62
     * about the error's position in the source.
63
     *
64
     * @return string
65
     */
66 12
    public static function printError(Error $error)
67
    {
68 12
        $printedLocations = [];
69 12
        if ($error->nodes) {
70
            /** @var Node $node */
71 1
            foreach ($error->nodes as $node) {
72 1
                if (! $node->loc) {
73
                    continue;
74
                }
75
76 1
                if ($node->loc->source === null) {
77
                    continue;
78
                }
79
80 1
                $printedLocations[] = self::highlightSourceAtLocation(
81 1
                    $node->loc->source,
82 1
                    $node->loc->source->getLocation($node->loc->start)
83
                );
84
            }
85 11
        } elseif ($error->getSource() && $error->getLocations()) {
86 10
            $source = $error->getSource();
87 10
            foreach ($error->getLocations() as $location) {
88 10
                $printedLocations[] = self::highlightSourceAtLocation($source, $location);
89
            }
90
        }
91
92 12
        return ! $printedLocations
93 1
            ? $error->getMessage()
94 12
            : implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
95
    }
96
97
    /**
98
     * Render a helpful description of the location of the error in the GraphQL
99
     * Source document.
100
     *
101
     * @return string
102
     */
103 11
    private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
104
    {
105 11
        $line          = $location->line;
106 11
        $lineOffset    = $source->locationOffset->line - 1;
107 11
        $columnOffset  = self::getColumnOffset($source, $location);
108 11
        $contextLine   = $line + $lineOffset;
109 11
        $contextColumn = $location->column + $columnOffset;
110 11
        $prevLineNum   = (string) ($contextLine - 1);
111 11
        $lineNum       = (string) $contextLine;
112 11
        $nextLineNum   = (string) ($contextLine + 1);
113 11
        $padLen        = strlen($nextLineNum);
114 11
        $lines         = preg_split('/\r\n|[\n\r]/', $source->body);
115
116 11
        $lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
117
118
        $outputLines = [
119 11
            sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
120 11
            $line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
121 11
            self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
122 11
            self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
123 11
            $line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
0 ignored issues
show
Bug introduced by
It seems like $lines can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
            $line < count(/** @scrutinizer ignore-type */ $lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
Loading history...
124
        ];
125
126 11
        return implode("\n", array_filter($outputLines));
127
    }
128
129
    /**
130
     * @return int
131
     */
132 11
    private static function getColumnOffset(Source $source, SourceLocation $location)
133
    {
134 11
        return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
135
    }
136
137
    /**
138
     * @param int $len
139
     *
140
     * @return string
141
     */
142 11
    private static function whitespace($len)
143
    {
144 11
        return str_repeat(' ', $len);
145
    }
146
147
    /**
148
     * @param int $len
149
     *
150
     * @return string
151
     */
152 11
    private static function lpad($len, $str)
153
    {
154 11
        return self::whitespace($len - mb_strlen($str)) . $str;
155
    }
156
157
    /**
158
     * Standard GraphQL error formatter. Converts any exception to array
159
     * conforming to GraphQL spec.
160
     *
161
     * This method only exposes exception message when exception implements ClientAware interface
162
     * (or when debug flags are passed).
163
     *
164
     * For a list of available debug flags see GraphQL\Error\Debug constants.
165
     *
166
     * @param Throwable $e
167
     * @param bool|int  $debug
168
     * @param string    $internalErrorMessage
169
     *
170
     * @return mixed[]
171
     *
172
     * @throws Throwable
173
     *
174
     * @api
175
     */
176 74
    public static function createFromException($e, $debug = false, $internalErrorMessage = null)
177
    {
178 74
        Utils::invariant(
179 74
            $e instanceof Throwable,
180 74
            'Expected exception, got %s',
181 74
            Utils::getVariableType($e)
182
        );
183
184 74
        $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage;
185
186 74
        if ($e instanceof ClientAware) {
187
            $formattedError = [
188 74
                'message'  => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
189
                'extensions' => [
190 74
                    'category' => $e->getCategory(),
191
                ],
192
            ];
193
        } else {
194
            $formattedError = [
195
                'message'  => $internalErrorMessage,
196
                'extensions' => [
197
                    'category' => Error::CATEGORY_INTERNAL,
198
                ],
199
            ];
200
        }
201
202 74
        if ($e instanceof Error) {
203 74
            $locations = Utils::map(
204 74
                $e->getLocations(),
205
                static function (SourceLocation $loc) : array {
206 66
                    return $loc->toSerializableArray();
207 74
                }
208
            );
209 74
            if (! empty($locations)) {
210 66
                $formattedError['locations'] = $locations;
211
            }
212 74
            if (! empty($e->path)) {
213 50
                $formattedError['path'] = $e->path;
214
            }
215 74
            if (! empty($e->getExtensions())) {
216 1
                $formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
217
            }
218
        }
219
220 74
        if ($debug) {
221
            $formattedError = self::addDebugEntries($formattedError, $e, $debug);
222
        }
223
224 74
        return $formattedError;
225
    }
226
227
    /**
228
     * Decorates spec-compliant $formattedError with debug entries according to $debug flags
229
     * (see GraphQL\Error\Debug for available flags)
230
     *
231
     * @param mixed[]   $formattedError
232
     * @param Throwable $e
233
     * @param bool|int  $debug
234
     *
235
     * @return mixed[]
236
     *
237
     * @throws Throwable
238
     */
239 27
    public static function addDebugEntries(array $formattedError, $e, $debug)
240
    {
241 27
        if (! $debug) {
242
            return $formattedError;
243
        }
244
245 27
        Utils::invariant(
246 27
            $e instanceof Throwable,
247 27
            'Expected exception, got %s',
248 27
            Utils::getVariableType($e)
249
        );
250
251 27
        $debug = (int) $debug;
252
253 27
        if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
254
            if (! $e instanceof Error) {
255
                throw $e;
256
            }
257
258
            if ($e->getPrevious()) {
259
                throw $e->getPrevious();
260
            }
261
        }
262
263 27
        $isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
264
265 27
        if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
266 1
            if ($e->getPrevious()) {
267 1
                throw $e->getPrevious();
268
            }
269
        }
270
271 26
        if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
272
            // Displaying debugMessage as a first entry:
273 22
            $formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
274
        }
275
276 26
        if ($debug & Debug::INCLUDE_TRACE) {
277 2
            if ($e instanceof ErrorException || $e instanceof \Error) {
278
                $formattedError += [
279
                    'file' => $e->getFile(),
280
                    'line' => $e->getLine(),
281
                ];
282
            }
283
284 2
            $isTrivial = $e instanceof Error && ! $e->getPrevious();
285
286 2
            if (! $isTrivial) {
287 2
                $debugging               = $e->getPrevious() ?: $e;
288 2
                $formattedError['trace'] = static::toSafeTrace($debugging);
289
            }
290
        }
291
292 26
        return $formattedError;
293
    }
294
295
    /**
296
     * Prepares final error formatter taking in account $debug flags.
297
     * If initial formatter is not set, FormattedError::createFromException is used
298
     *
299
     * @param  bool|int $debug
300
     *
301
     * @return callable|callable
302
     */
303 76
    public static function prepareFormatter(?callable $formatter = null, $debug)
304
    {
305
        $formatter = $formatter ?: static function ($e) : array {
306 74
            return FormattedError::createFromException($e);
307 76
        };
308 76
        if ($debug) {
309
            $formatter = static function ($e) use ($formatter, $debug) : array {
310 27
                return FormattedError::addDebugEntries($formatter($e), $e, $debug);
311 27
            };
312
        }
313
314 76
        return $formatter;
315
    }
316
317
    /**
318
     * Returns error trace as serializable array
319
     *
320
     * @param Throwable $error
321
     *
322
     * @return mixed[]
323
     *
324
     * @api
325
     */
326 2
    public static function toSafeTrace($error)
327
    {
328 2
        $trace = $error->getTrace();
329
330 2
        if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
331
            // Remove invariant entries as they don't provide much value:
332 2
            ($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
333
            array_shift($trace);
334 2
        } elseif (! isset($trace[0]['file'])) {
335
            // Remove root call as it's likely error handler trace:
336
            array_shift($trace);
337
        }
338
339 2
        return array_map(
340
            static function ($err) : array {
341 2
                $safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
342
343 2
                if (isset($err['function'])) {
344 2
                    $func    = $err['function'];
345 2
                    $args    = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
346 2
                    $funcStr = $func . '(' . implode(', ', $args) . ')';
347
348 2
                    if (isset($err['class'])) {
349 2
                        $safeErr['call'] = $err['class'] . '::' . $funcStr;
350
                    } else {
351
                        $safeErr['function'] = $funcStr;
352
                    }
353
                }
354
355 2
                return $safeErr;
356 2
            },
357 2
            $trace
358
        );
359
    }
360
361
    /**
362
     * @param mixed $var
363
     *
364
     * @return string
365
     */
366 2
    public static function printVar($var)
367
    {
368 2
        if ($var instanceof Type) {
369
            // FIXME: Replace with schema printer call
370 2
            if ($var instanceof WrappingType) {
371
                $var = $var->getWrappedType(true);
372
            }
373
374 2
            return 'GraphQLType: ' . $var->name;
375
        }
376
377 2
        if (is_object($var)) {
378 2
            return 'instance of ' . get_class($var) . ($var instanceof Countable ? '(' . count($var) . ')' : '');
379
        }
380 2
        if (is_array($var)) {
381 2
            return 'array(' . count($var) . ')';
382
        }
383 2
        if ($var === '') {
384
            return '(empty string)';
385
        }
386 2
        if (is_string($var)) {
387 2
            return "'" . addcslashes($var, "'") . "'";
388
        }
389 2
        if (is_bool($var)) {
390 2
            return $var ? 'true' : 'false';
391
        }
392 2
        if (is_scalar($var)) {
393
            return $var;
394
        }
395 2
        if ($var === null) {
396 2
            return 'null';
397
        }
398
399
        return gettype($var);
400
    }
401
402
    /**
403
     * @deprecated as of v0.8.0
404
     *
405
     * @param string           $error
406
     * @param SourceLocation[] $locations
407
     *
408
     * @return mixed[]
409
     */
410 200
    public static function create($error, array $locations = [])
411
    {
412 200
        $formatted = ['message' => $error];
413
414 200
        if (! empty($locations)) {
415 185
            $formatted['locations'] = array_map(
416
                static function ($loc) : array {
417 185
                    return $loc->toArray();
418 185
                },
419 185
                $locations
420
            );
421
        }
422
423 200
        return $formatted;
424
    }
425
426
    /**
427
     * @deprecated as of v0.10.0, use general purpose method createFromException() instead
428
     *
429
     * @return mixed[]
430
     *
431
     * @codeCoverageIgnore
432
     */
433
    public static function createFromPHPError(ErrorException $e)
434
    {
435
        return [
436
            'message'  => $e->getMessage(),
437
            'severity' => $e->getSeverity(),
438
            'trace'    => self::toSafeTrace($e),
439
        ];
440
    }
441
}
442