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.
Completed
Pull Request — master (#1)
by Šimon
29:17 queued 25:57
created

FormattedError::toSafeTrace()   C

Complexity

Conditions 8
Paths 3

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 8.2964

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 15
cts 18
cp 0.8333
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 19
nc 3
nop 1
crap 8.2964
1
<?php
2
3
namespace GraphQL\Error;
4
5
use GraphQL\Language\AST\Node;
6
use GraphQL\Language\Source;
7
use GraphQL\Language\SourceLocation;
8
use GraphQL\Type\Definition\Type;
9
use GraphQL\Type\Definition\WrappingType;
10
use GraphQL\Utils\Utils;
11
12
/**
13
 * This class is used for [default error formatting](error-handling.md).
14
 * It converts PHP exceptions to [spec-compliant errors](https://facebook.github.io/graphql/#sec-Errors)
15
 * and provides tools for error debugging.
16
 */
17
class FormattedError
18
{
19
    private static $internalErrorMessage = 'Internal server error';
20
21
    /**
22
     * Set default error message for internal errors formatted using createFormattedError().
23
     * This value can be overridden by passing 3rd argument to `createFormattedError()`.
24
     *
25
     * @api
26
     * @param string $msg
27
     */
28
    public static function setInternalErrorMessage($msg)
29
    {
30
        self::$internalErrorMessage = $msg;
31
    }
32
33
    /**
34
     * Prints a GraphQLError to a string, representing useful location information
35
     * about the error's position in the source.
36
     *
37
     * @param Error $error
38
     * @return string
39
     */
40 11
    public static function printError(Error $error)
41
    {
42 11
        $printedLocations = [];
43 11
        if ($error->nodes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $error->nodes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
44
            /** @var Node $node */
45 1
            foreach ($error->nodes as $node) {
46 1
                if ($node->loc) {
47 1
                    $printedLocations[] = self::highlightSourceAtLocation(
48 1
                        $node->loc->source,
49 1
                        $node->loc->source->getLocation($node->loc->start)
50
                    );
51
                }
52
            }
53 10
        } elseif ($error->getSource() && $error->getLocations()) {
54 9
            $source = $error->getSource();
55 9
            foreach ($error->getLocations() as $location) {
56 9
                $printedLocations[] = self::highlightSourceAtLocation($source, $location);
57
            }
58
        }
59
60 11
        return ! $printedLocations
61 1
            ? $error->getMessage()
62 11
            : join("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
63
    }
64
65
    /**
66
     * Render a helpful description of the location of the error in the GraphQL
67
     * Source document.
68
     *
69
     * @param Source         $source
70
     * @param SourceLocation $location
71
     * @return string
72
     */
73 10
    private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
74
    {
75 10
        $line          = $location->line;
76 10
        $lineOffset    = $source->locationOffset->line - 1;
77 10
        $columnOffset  = self::getColumnOffset($source, $location);
78 10
        $contextLine   = $line + $lineOffset;
79 10
        $contextColumn = $location->column + $columnOffset;
80 10
        $prevLineNum   = (string) ($contextLine - 1);
81 10
        $lineNum       = (string) $contextLine;
82 10
        $nextLineNum   = (string) ($contextLine + 1);
83 10
        $padLen        = strlen($nextLineNum);
84 10
        $lines         = preg_split('/\r\n|[\n\r]/', $source->body);
85
86 10
        $lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
87
88
        $outputLines = [
89 10
            "{$source->name} ($contextLine:$contextColumn)",
90 10
            $line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
91 10
            self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
92 10
            self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
93 10
            $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

93
            $line < count(/** @scrutinizer ignore-type */ $lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
Loading history...
94
        ];
95
96 10
        return join("\n", array_filter($outputLines));
97
    }
98
99
    /**
100
     * @param Source         $source
101
     * @param SourceLocation $location
102
     * @return int
103
     */
104 10
    private static function getColumnOffset(Source $source, SourceLocation $location)
105
    {
106 10
        return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
107
    }
108
109
    /**
110
     * @param int $len
111
     * @return string
112
     */
113 10
    private static function whitespace($len)
114
    {
115 10
        return str_repeat(' ', $len);
116
    }
117
118
    /**
119
     * @param int $len
120
     * @return string
121
     */
122 10
    private static function lpad($len, $str)
123
    {
124 10
        return self::whitespace($len - mb_strlen($str)) . $str;
125
    }
126
127
    /**
128
     * Standard GraphQL error formatter. Converts any exception to array
129
     * conforming to GraphQL spec.
130
     *
131
     * This method only exposes exception message when exception implements ClientAware interface
132
     * (or when debug flags are passed).
133
     *
134
     * For a list of available debug flags see GraphQL\Error\Debug constants.
135
     *
136
     * @api
137
     * @param \Throwable $e
138
     * @param bool|int   $debug
139
     * @param string     $internalErrorMessage
140
     * @return array
141
     * @throws \Throwable
142
     */
143 65
    public static function createFromException($e, $debug = false, $internalErrorMessage = null)
144
    {
145 65
        Utils::invariant(
146 65
            $e instanceof \Exception || $e instanceof \Throwable,
147 65
            "Expected exception, got %s",
148 65
            Utils::getVariableType($e)
149
        );
150
151 65
        $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage;
152
153 65
        if ($e instanceof ClientAware) {
154
            $formattedError = [
155 65
                'message'  => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on GraphQL\Error\ClientAware. Since it exists in all sub-types, consider adding an abstract or default implementation to GraphQL\Error\ClientAware. ( Ignorable by Annotation )

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

155
                'message'  => $e->isClientSafe() ? $e->/** @scrutinizer ignore-call */ getMessage() : $internalErrorMessage,
Loading history...
156 65
                'category' => $e->getCategory(),
157
            ];
158
        } else {
159
            $formattedError = [
160
                'message'  => $internalErrorMessage,
161
                'category' => Error::CATEGORY_INTERNAL,
162
            ];
163
        }
164
165 65
        if ($e instanceof Error) {
166 65
            if ($e->getExtensions()) {
167
                $formattedError = array_merge($e->getExtensions(), $formattedError);
168
            }
169
170 65
            $locations = Utils::map($e->getLocations(),
171
                function (SourceLocation $loc) {
172 58
                    return $loc->toSerializableArray();
173 65
                });
174
175 65
            if (! empty($locations)) {
176 58
                $formattedError['locations'] = $locations;
177
            }
178 65
            if (! empty($e->path)) {
179 44
                $formattedError['path'] = $e->path;
180
            }
181
        }
182
183 65
        if ($debug) {
184
            $formattedError = self::addDebugEntries($formattedError, $e, $debug);
0 ignored issues
show
Bug introduced by
It seems like $debug can also be of type integer; however, parameter $debug of GraphQL\Error\FormattedError::addDebugEntries() does only seem to accept boolean, 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

184
            $formattedError = self::addDebugEntries($formattedError, $e, /** @scrutinizer ignore-type */ $debug);
Loading history...
185
        }
186
187 65
        return $formattedError;
188
    }
189
190
    /**
191
     * Decorates spec-compliant $formattedError with debug entries according to $debug flags
192
     * (see GraphQL\Error\Debug for available flags)
193
     *
194
     * @param array      $formattedError
195
     * @param \Throwable $e
196
     * @param bool       $debug
197
     * @return array
198
     * @throws \Throwable
199
     */
200 26
    public static function addDebugEntries(array $formattedError, $e, $debug)
201
    {
202 26
        if (! $debug) {
203
            return $formattedError;
204
        }
205
206 26
        Utils::invariant(
207 26
            $e instanceof \Exception || $e instanceof \Throwable,
208 26
            "Expected exception, got %s",
209 26
            Utils::getVariableType($e)
210
        );
211
212 26
        $debug = (int) $debug;
213
214 26
        if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
215
            if (! $e instanceof Error) {
216
                throw $e;
217
            } elseif ($e->getPrevious()) {
218
                throw $e->getPrevious();
219
            }
220
        }
221
222 26
        $isInternal = ! $e instanceof ClientAware || ! $e->isClientSafe();
223
224 26
        if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
225
            // Displaying debugMessage as a first entry:
226 21
            $formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
227
        }
228
229 26
        if ($debug & Debug::INCLUDE_TRACE) {
230 2
            if ($e instanceof \ErrorException || $e instanceof \Error) {
231
                $formattedError += [
232
                    'file' => $e->getFile(),
233
                    'line' => $e->getLine(),
234
                ];
235
            }
236
237 2
            $isTrivial = $e instanceof Error && ! $e->getPrevious();
238
239 2
            if (! $isTrivial) {
240 2
                $debugging               = $e->getPrevious() ?: $e;
241 2
                $formattedError['trace'] = static::toSafeTrace($debugging);
242
            }
243
        }
244
245 26
        return $formattedError;
246
    }
247
248
    /**
249
     * Prepares final error formatter taking in account $debug flags.
250
     * If initial formatter is not set, FormattedError::createFromException is used
251
     *
252
     * @param callable|null $formatter
253
     * @param               $debug
254
     * @return callable|\Closure
255
     */
256 67
    public static function prepareFormatter(callable $formatter = null, $debug)
257
    {
258
        $formatter = $formatter ?: function ($e) {
259 65
            return FormattedError::createFromException($e);
260 67
        };
261 67
        if ($debug) {
262
            $formatter = function ($e) use ($formatter, $debug) {
263 26
                return FormattedError::addDebugEntries($formatter($e), $e, $debug);
264 26
            };
265
        }
266
267 67
        return $formatter;
268
    }
269
270
    /**
271
     * Returns error trace as serializable array
272
     *
273
     * @api
274
     * @param \Throwable $error
275
     * @return array
276
     */
277 2
    public static function toSafeTrace($error)
278
    {
279 2
        $trace = $error->getTrace();
280
281
        // Remove invariant entries as they don't provide much value:
282
        if (
283 2
            isset($trace[0]['function']) && isset($trace[0]['class']) &&
284 2
            ('GraphQL\Utils\Utils::invariant' === $trace[0]['class'] . '::' . $trace[0]['function'])) {
285
            array_shift($trace);
286
        } // Remove root call as it's likely error handler trace:
287 2
        elseif (! isset($trace[0]['file'])) {
288
            array_shift($trace);
289
        }
290
291
        return array_map(function ($err) {
292 2
            $safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
293
294 2
            if (isset($err['function'])) {
295 2
                $func    = $err['function'];
296 2
                $args    = ! empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
297 2
                $funcStr = $func . '(' . implode(", ", $args) . ')';
298
299 2
                if (isset($err['class'])) {
300 2
                    $safeErr['call'] = $err['class'] . '::' . $funcStr;
301
                } else {
302
                    $safeErr['function'] = $funcStr;
303
                }
304
            }
305
306 2
            return $safeErr;
307 2
        },
308 2
            $trace);
309
    }
310
311
    /**
312
     * @param $var
313
     * @return string
314
     */
315 2
    public static function printVar($var)
316
    {
317 2
        if ($var instanceof Type) {
318
            // FIXME: Replace with schema printer call
319 2
            if ($var instanceof WrappingType) {
320
                $var = $var->getWrappedType(true);
321
            }
322
323 2
            return 'GraphQLType: ' . $var->name;
324
        }
325
326 2
        if (is_object($var)) {
327 2
            return 'instance of ' . get_class($var) . ($var instanceof \Countable ? '(' . count($var) . ')' : '');
328
        }
329 2
        if (is_array($var)) {
330 2
            return 'array(' . count($var) . ')';
331
        }
332 2
        if ('' === $var) {
333
            return '(empty string)';
334
        }
335 2
        if (is_string($var)) {
336 2
            return "'" . addcslashes($var, "'") . "'";
337
        }
338 2
        if (is_bool($var)) {
339 2
            return $var ? 'true' : 'false';
340
        }
341 2
        if (is_scalar($var)) {
342
            return $var;
343
        }
344 2
        if (null === $var) {
345 2
            return 'null';
346
        }
347
348
        return gettype($var);
349
    }
350
351
    /**
352
     * @deprecated as of v0.8.0
353
     * @param                  $error
354
     * @param SourceLocation[] $locations
355
     * @return array
356
     */
357
    public static function create($error, array $locations = [])
358
    {
359
        $formatted = [
360
            'message' => $error,
361
        ];
362
363
        if (! empty($locations)) {
364
            $formatted['locations'] = array_map(function ($loc) {
365
                return $loc->toArray();
366
            },
367
                $locations);
368
        }
369
370
        return $formatted;
371
    }
372
373
    /**
374
     * @param \ErrorException $e
375
     * @deprecated as of v0.10.0, use general purpose method createFromException() instead
376
     * @return array
377
     */
378
    public static function createFromPHPError(\ErrorException $e)
379
    {
380
        return [
381
            'message'  => $e->getMessage(),
382
            'severity' => $e->getSeverity(),
383
            'trace'    => self::toSafeTrace($e),
384
        ];
385
    }
386
}
387